Пример #1
0
    def get_message_vars(self, notif, event, room_state_ids):
        if event.type != EventTypes.Message:
            return

        sender_state_event_id = room_state_ids[("m.room.member", event.sender)]
        sender_state_event = yield self.store.get_event(sender_state_event_id)
        sender_name = name_from_member_event(sender_state_event)
        sender_avatar_url = sender_state_event.content.get("avatar_url")

        # 'hash' for deterministically picking default images: use
        # sender_hash % the number of default images to choose from
        sender_hash = string_ordinal_total(event.sender)

        msgtype = event.content.get("msgtype")

        ret = {
            "msgtype": msgtype,
            "is_historical": event.event_id != notif['event_id'],
            "id": event.event_id,
            "ts": event.origin_server_ts,
            "sender_name": sender_name,
            "sender_avatar_url": sender_avatar_url,
            "sender_hash": sender_hash,
        }

        if msgtype == "m.text":
            self.add_text_message_vars(ret, event)
        elif msgtype == "m.image":
            self.add_image_message_vars(ret, event)

        if "body" in event.content:
            ret["body_text_plain"] = event.content["body"]

        defer.returnValue(ret)
Пример #2
0
    def get_message_vars(self, notif, event, room_state_ids):
        if event.type != EventTypes.Message:
            return

        sender_state_event_id = room_state_ids[("m.room.member", event.sender)]
        sender_state_event = yield self.store.get_event(sender_state_event_id)
        sender_name = name_from_member_event(sender_state_event)
        sender_avatar_url = sender_state_event.content.get("avatar_url")

        # 'hash' for deterministically picking default images: use
        # sender_hash % the number of default images to choose from
        sender_hash = string_ordinal_total(event.sender)

        msgtype = event.content.get("msgtype")

        ret = {
            "msgtype": msgtype,
            "is_historical": event.event_id != notif['event_id'],
            "id": event.event_id,
            "ts": event.origin_server_ts,
            "sender_name": sender_name,
            "sender_avatar_url": sender_avatar_url,
            "sender_hash": sender_hash,
        }

        if msgtype == "m.text":
            self.add_text_message_vars(ret, event)
        elif msgtype == "m.image":
            self.add_image_message_vars(ret, event)

        if "body" in event.content:
            ret["body_text_plain"] = event.content["body"]

        defer.returnValue(ret)
Пример #3
0
def get_context_for_event(store, state_handler, ev, user_id):
    ctx = {}

    room_state_ids = yield store.get_state_ids_for_event(ev.event_id)

    # we no longer bother setting room_alias, and make room_name the
    # human-readable name instead, be that m.room.name, an alias or
    # a list of people in the room
    name = yield calculate_room_name(
        store, room_state_ids, user_id, fallback_to_single_member=False
    )
    if name:
        ctx["name"] = name

    sender_state_event_id = room_state_ids[("m.room.member", ev.sender)]
    sender_state_event = yield store.get_event(sender_state_event_id)
    ctx["sender_display_name"] = name_from_member_event(sender_state_event)

    return ctx
Пример #4
0
def get_context_for_event(store, state_handler, ev, user_id):
    ctx = {}

    room_state_ids = yield state_handler.get_current_state_ids(ev.room_id)

    # we no longer bother setting room_alias, and make room_name the
    # human-readable name instead, be that m.room.name, an alias or
    # a list of people in the room
    name = yield calculate_room_name(
        store, room_state_ids, user_id, fallback_to_single_member=False
    )
    if name:
        ctx['name'] = name

    sender_state_event_id = room_state_ids[("m.room.member", ev.sender)]
    sender_state_event = yield store.get_event(sender_state_event_id)
    ctx['sender_display_name'] = name_from_member_event(sender_state_event)

    defer.returnValue(ctx)
Пример #5
0
    async def get_message_vars(
            self, notif: Dict[str, Any], event: EventBase,
            room_state_ids: StateMap[str]) -> Optional[Dict[str, Any]]:
        if event.type != EventTypes.Message and event.type != EventTypes.Encrypted:
            return None

        sender_state_event_id = room_state_ids[("m.room.member", event.sender)]
        sender_state_event = await self.store.get_event(sender_state_event_id)
        sender_name = name_from_member_event(sender_state_event)
        sender_avatar_url = sender_state_event.content.get("avatar_url")

        # 'hash' for deterministically picking default images: use
        # sender_hash % the number of default images to choose from
        sender_hash = string_ordinal_total(event.sender)

        ret = {
            "event_type": event.type,
            "is_historical": event.event_id != notif["event_id"],
            "id": event.event_id,
            "ts": event.origin_server_ts,
            "sender_name": sender_name,
            "sender_avatar_url": sender_avatar_url,
            "sender_hash": sender_hash,
        }

        # Encrypted messages don't have any additional useful information.
        if event.type == EventTypes.Encrypted:
            return ret

        msgtype = event.content.get("msgtype")

        ret["msgtype"] = msgtype

        if msgtype == "m.text":
            self.add_text_message_vars(ret, event)
        elif msgtype == "m.image":
            self.add_image_message_vars(ret, event)

        if "body" in event.content:
            ret["body_text_plain"] = event.content["body"]

        return ret
Пример #6
0
async def get_context_for_event(storage: Storage, ev: EventBase,
                                user_id: str) -> Dict[str, str]:
    ctx = {}

    room_state_ids = await storage.state.get_state_ids_for_event(ev.event_id)

    # we no longer bother setting room_alias, and make room_name the
    # human-readable name instead, be that m.room.name, an alias or
    # a list of people in the room
    name = await calculate_room_name(storage.main,
                                     room_state_ids,
                                     user_id,
                                     fallback_to_single_member=False)
    if name:
        ctx["name"] = name

    sender_state_event_id = room_state_ids[("m.room.member", ev.sender)]
    sender_state_event = await storage.main.get_event(sender_state_event_id)
    ctx["sender_display_name"] = name_from_member_event(sender_state_event)

    return ctx
Пример #7
0
    def make_summary_text(self, notifs_by_room, room_state_ids, notif_events,
                          user_id, reason):
        if len(notifs_by_room) == 1:
            # Only one room has new stuff
            room_id = notifs_by_room.keys()[0]

            # If the room has some kind of name, use it, but we don't
            # want the generated-from-names one here otherwise we'll
            # end up with, "new message from Bob in the Bob room"
            room_name = yield calculate_room_name(self.store,
                                                  room_state_ids[room_id],
                                                  user_id,
                                                  fallback_to_members=False)

            my_member_event_id = room_state_ids[room_id][("m.room.member",
                                                          user_id)]
            my_member_event = yield self.store.get_event(my_member_event_id)
            if my_member_event.content["membership"] == "invite":
                inviter_member_event_id = room_state_ids[room_id][(
                    "m.room.member", my_member_event.sender)]
                inviter_member_event = yield self.store.get_event(
                    inviter_member_event_id)
                inviter_name = name_from_member_event(inviter_member_event)

                if room_name is None:
                    defer.returnValue(INVITE_FROM_PERSON % {
                        "person": inviter_name,
                        "app": self.app_name
                    })
                else:
                    defer.returnValue(
                        INVITE_FROM_PERSON_TO_ROOM % {
                            "person": inviter_name,
                            "room": room_name,
                            "app": self.app_name,
                        })

            sender_name = None
            if len(notifs_by_room[room_id]) == 1:
                # There is just the one notification, so give some detail
                event = notif_events[notifs_by_room[room_id][0]["event_id"]]
                if ("m.room.member", event.sender) in room_state_ids[room_id]:
                    state_event_id = room_state_ids[room_id][("m.room.member",
                                                              event.sender)]
                    state_event = yield self.store.get_event(state_event_id)
                    sender_name = name_from_member_event(state_event)

                if sender_name is not None and room_name is not None:
                    defer.returnValue(
                        MESSAGE_FROM_PERSON_IN_ROOM % {
                            "person": sender_name,
                            "room": room_name,
                            "app": self.app_name,
                        })
                elif sender_name is not None:
                    defer.returnValue(MESSAGE_FROM_PERSON % {
                        "person": sender_name,
                        "app": self.app_name,
                    })
            else:
                # There's more than one notification for this room, so just
                # say there are several
                if room_name is not None:
                    defer.returnValue(MESSAGES_IN_ROOM % {
                        "room": room_name,
                        "app": self.app_name,
                    })
                else:
                    # If the room doesn't have a name, say who the messages
                    # are from explicitly to avoid, "messages in the Bob room"
                    sender_ids = list(
                        set([
                            notif_events[n['event_id']].sender
                            for n in notifs_by_room[room_id]
                        ]))

                    member_events = yield self.store.get_events([
                        room_state_ids[room_id][("m.room.member", s)]
                        for s in sender_ids
                    ])

                    defer.returnValue(
                        MESSAGES_FROM_PERSON % {
                            "person":
                            descriptor_from_member_events(
                                member_events.values()),
                            "app":
                            self.app_name,
                        })
        else:
            # Stuff's happened in multiple different rooms

            # ...but we still refer to the 'reason' room which triggered the mail
            if reason['room_name'] is not None:
                defer.returnValue(MESSAGES_IN_ROOM_AND_OTHERS % {
                    "room": reason['room_name'],
                    "app": self.app_name,
                })
            else:
                # If the reason room doesn't have a name, say who the messages
                # are from explicitly to avoid, "messages in the Bob room"
                sender_ids = list(
                    set([
                        notif_events[n['event_id']].sender
                        for n in notifs_by_room[reason['room_id']]
                    ]))

                member_events = yield self.store.get_events([
                    room_state_ids[room_id][("m.room.member", s)]
                    for s in sender_ids
                ])

                defer.returnValue(
                    MESSAGES_FROM_PERSON_AND_OTHERS % {
                        "person":
                        descriptor_from_member_events(member_events.values()),
                        "app":
                        self.app_name,
                    })
Пример #8
0
    async def make_summary_text(
        self, notifs_by_room, room_state_ids, notif_events, user_id, reason
    ):
        if len(notifs_by_room) == 1:
            # Only one room has new stuff
            room_id = list(notifs_by_room.keys())[0]

            # If the room has some kind of name, use it, but we don't
            # want the generated-from-names one here otherwise we'll
            # end up with, "new message from Bob in the Bob room"
            room_name = await calculate_room_name(
                self.store, room_state_ids[room_id], user_id, fallback_to_members=False
            )

            my_member_event_id = room_state_ids[room_id][("m.room.member", user_id)]
            my_member_event = await self.store.get_event(my_member_event_id)
            if my_member_event.content["membership"] == "invite":
                inviter_member_event_id = room_state_ids[room_id][
                    ("m.room.member", my_member_event.sender)
                ]
                inviter_member_event = await self.store.get_event(
                    inviter_member_event_id
                )
                inviter_name = name_from_member_event(inviter_member_event)

                if room_name is None:
                    return self.email_subjects.invite_from_person % {
                        "person": inviter_name,
                        "app": self.app_name,
                    }
                else:
                    return self.email_subjects.invite_from_person_to_room % {
                        "person": inviter_name,
                        "room": room_name,
                        "app": self.app_name,
                    }

            sender_name = None
            if len(notifs_by_room[room_id]) == 1:
                # There is just the one notification, so give some detail
                event = notif_events[notifs_by_room[room_id][0]["event_id"]]
                if ("m.room.member", event.sender) in room_state_ids[room_id]:
                    state_event_id = room_state_ids[room_id][
                        ("m.room.member", event.sender)
                    ]
                    state_event = await self.store.get_event(state_event_id)
                    sender_name = name_from_member_event(state_event)

                if sender_name is not None and room_name is not None:
                    return self.email_subjects.message_from_person_in_room % {
                        "person": sender_name,
                        "room": room_name,
                        "app": self.app_name,
                    }
                elif sender_name is not None:
                    return self.email_subjects.message_from_person % {
                        "person": sender_name,
                        "app": self.app_name,
                    }
            else:
                # There's more than one notification for this room, so just
                # say there are several
                if room_name is not None:
                    return self.email_subjects.messages_in_room % {
                        "room": room_name,
                        "app": self.app_name,
                    }
                else:
                    # If the room doesn't have a name, say who the messages
                    # are from explicitly to avoid, "messages in the Bob room"
                    sender_ids = list(
                        {
                            notif_events[n["event_id"]].sender
                            for n in notifs_by_room[room_id]
                        }
                    )

                    member_events = await self.store.get_events(
                        [
                            room_state_ids[room_id][("m.room.member", s)]
                            for s in sender_ids
                        ]
                    )

                    return self.email_subjects.messages_from_person % {
                        "person": descriptor_from_member_events(member_events.values()),
                        "app": self.app_name,
                    }
        else:
            # Stuff's happened in multiple different rooms

            # ...but we still refer to the 'reason' room which triggered the mail
            if reason["room_name"] is not None:
                return self.email_subjects.messages_in_room_and_others % {
                    "room": reason["room_name"],
                    "app": self.app_name,
                }
            else:
                # If the reason room doesn't have a name, say who the messages
                # are from explicitly to avoid, "messages in the Bob room"
                room_id = reason["room_id"]

                sender_ids = list(
                    {
                        notif_events[n["event_id"]].sender
                        for n in notifs_by_room[room_id]
                    }
                )

                member_events = await self.store.get_events(
                    [room_state_ids[room_id][("m.room.member", s)] for s in sender_ids]
                )

                return self.email_subjects.messages_from_person_and_others % {
                    "person": descriptor_from_member_events(member_events.values()),
                    "app": self.app_name,
                }
Пример #9
0
    async def _make_summary_text_single_room(
        self,
        room_id: str,
        notifs: List[EmailPushAction],
        room_state_ids: StateMap[str],
        notif_events: Dict[str, EventBase],
        user_id: str,
    ) -> str:
        """
        Make a summary text for the email when only a single room has notifications.

        Args:
            room_id: The ID of the room.
            notifs: The push actions for this room.
            room_state_ids: The state map for the room.
            notif_events: A map of event ID -> notification event.
            user_id: The user receiving the notification.

        Returns:
            The summary text.
        """
        # If the room has some kind of name, use it, but we don't
        # want the generated-from-names one here otherwise we'll
        # end up with, "new message from Bob in the Bob room"
        room_name = await calculate_room_name(self.store,
                                              room_state_ids,
                                              user_id,
                                              fallback_to_members=False)

        # See if one of the notifs is an invite event for the user
        invite_event = None
        for n in notifs:
            ev = notif_events[n.event_id]
            if ev.type == EventTypes.Member and ev.state_key == user_id:
                if ev.content.get("membership") == Membership.INVITE:
                    invite_event = ev
                    break

        if invite_event:
            inviter_member_event_id = room_state_ids.get(
                ("m.room.member", invite_event.sender))
            inviter_name = invite_event.sender
            if inviter_member_event_id:
                inviter_member_event = await self.store.get_event(
                    inviter_member_event_id, allow_none=True)
                if inviter_member_event:
                    inviter_name = name_from_member_event(inviter_member_event)

            if room_name is None:
                return self.email_subjects.invite_from_person % {
                    "person": inviter_name,
                    "app": self.app_name,
                }

            # If the room is a space, it gets a slightly different topic.
            create_event_id = room_state_ids.get(("m.room.create", ""))
            if create_event_id:
                create_event = await self.store.get_event(create_event_id,
                                                          allow_none=True)
                if (create_event and create_event.content.get("room_type")
                        == RoomTypes.SPACE):
                    return self.email_subjects.invite_from_person_to_space % {
                        "person": inviter_name,
                        "space": room_name,
                        "app": self.app_name,
                    }

            return self.email_subjects.invite_from_person_to_room % {
                "person": inviter_name,
                "room": room_name,
                "app": self.app_name,
            }

        if len(notifs) == 1:
            # There is just the one notification, so give some detail
            sender_name = None
            event = notif_events[notifs[0].event_id]
            if ("m.room.member", event.sender) in room_state_ids:
                state_event_id = room_state_ids[("m.room.member",
                                                 event.sender)]
                state_event = await self.store.get_event(state_event_id)
                sender_name = name_from_member_event(state_event)

            if sender_name is not None and room_name is not None:
                return self.email_subjects.message_from_person_in_room % {
                    "person": sender_name,
                    "room": room_name,
                    "app": self.app_name,
                }
            elif sender_name is not None:
                return self.email_subjects.message_from_person % {
                    "person": sender_name,
                    "app": self.app_name,
                }

            # The sender is unknown, just use the room name (or ID).
            return self.email_subjects.messages_in_room % {
                "room": room_name or room_id,
                "app": self.app_name,
            }
        else:
            # There's more than one notification for this room, so just
            # say there are several
            if room_name is not None:
                return self.email_subjects.messages_in_room % {
                    "room": room_name,
                    "app": self.app_name,
                }

            return await self._make_summary_text_from_member_events(
                room_id, notifs, room_state_ids, notif_events)
Пример #10
0
    async def _get_message_vars(
            self, notif: EmailPushAction, event: EventBase,
            room_state_ids: StateMap[str]) -> Optional[MessageVars]:
        """
        Generate the variables for a single event, if possible.

        Args:
            notif: The outstanding notification for this room.
            event: The event under consideration.
            room_state_ids: The event IDs of the current room state.

        Returns:
             A dictionary to be added to the template context, or None if the
             event cannot be processed.
        """
        if event.type != EventTypes.Message and event.type != EventTypes.Encrypted:
            return None

        # Get the sender's name and avatar from the room state.
        type_state_key = ("m.room.member", event.sender)
        sender_state_event_id = room_state_ids.get(type_state_key)
        if sender_state_event_id:
            sender_state_event: Optional[
                EventBase] = await self.store.get_event(sender_state_event_id)
        else:
            # Attempt to check the historical state for the room.
            historical_state = await self._state_storage_controller.get_state_for_event(
                event.event_id, StateFilter.from_types((type_state_key, )))
            sender_state_event = historical_state.get(type_state_key)

        if sender_state_event:
            sender_name = name_from_member_event(sender_state_event)
            sender_avatar_url: Optional[str] = sender_state_event.content.get(
                "avatar_url")
        else:
            # No state could be found, fallback to the MXID.
            sender_name = event.sender
            sender_avatar_url = None

        # 'hash' for deterministically picking default images: use
        # sender_hash % the number of default images to choose from
        sender_hash = string_ordinal_total(event.sender)

        ret: MessageVars = {
            "event_type": event.type,
            "is_historical": event.event_id != notif.event_id,
            "id": event.event_id,
            "ts": event.origin_server_ts,
            "sender_name": sender_name,
            "sender_avatar_url": sender_avatar_url,
            "sender_hash": sender_hash,
        }

        # Encrypted messages don't have any additional useful information.
        if event.type == EventTypes.Encrypted:
            return ret

        msgtype = event.content.get("msgtype")
        if not isinstance(msgtype, str):
            msgtype = None

        ret["msgtype"] = msgtype

        if msgtype == "m.text":
            self._add_text_message_vars(ret, event)
        elif msgtype == "m.image":
            self._add_image_message_vars(ret, event)

        if "body" in event.content:
            ret["body_text_plain"] = event.content["body"]

        return ret
Пример #11
0
    async def make_summary_text(
        self,
        notifs_by_room: Dict[str, List[Dict[str, Any]]],
        room_state_ids: Dict[str, StateMap[str]],
        notif_events: Dict[str, EventBase],
        user_id: str,
        reason: Dict[str, Any],
    ):
        if len(notifs_by_room) == 1:
            # Only one room has new stuff
            room_id = list(notifs_by_room.keys())[0]

            # If the room has some kind of name, use it, but we don't
            # want the generated-from-names one here otherwise we'll
            # end up with, "new message from Bob in the Bob room"
            room_name = await calculate_room_name(self.store,
                                                  room_state_ids[room_id],
                                                  user_id,
                                                  fallback_to_members=False)

            # See if one of the notifs is an invite event for the user
            invite_event = None
            for n in notifs_by_room[room_id]:
                ev = notif_events[n["event_id"]]
                if ev.type == EventTypes.Member and ev.state_key == user_id:
                    if ev.content.get("membership") == Membership.INVITE:
                        invite_event = ev
                        break

            if invite_event:
                inviter_member_event_id = room_state_ids[room_id].get(
                    ("m.room.member", invite_event.sender))
                inviter_name = invite_event.sender
                if inviter_member_event_id:
                    inviter_member_event = await self.store.get_event(
                        inviter_member_event_id, allow_none=True)
                    if inviter_member_event:
                        inviter_name = name_from_member_event(
                            inviter_member_event)

                if room_name is None:
                    return self.email_subjects.invite_from_person % {
                        "person": inviter_name,
                        "app": self.app_name,
                    }
                else:
                    return self.email_subjects.invite_from_person_to_room % {
                        "person": inviter_name,
                        "room": room_name,
                        "app": self.app_name,
                    }

            sender_name = None
            if len(notifs_by_room[room_id]) == 1:
                # There is just the one notification, so give some detail
                event = notif_events[notifs_by_room[room_id][0]["event_id"]]
                if ("m.room.member", event.sender) in room_state_ids[room_id]:
                    state_event_id = room_state_ids[room_id][("m.room.member",
                                                              event.sender)]
                    state_event = await self.store.get_event(state_event_id)
                    sender_name = name_from_member_event(state_event)

                if sender_name is not None and room_name is not None:
                    return self.email_subjects.message_from_person_in_room % {
                        "person": sender_name,
                        "room": room_name,
                        "app": self.app_name,
                    }
                elif sender_name is not None:
                    return self.email_subjects.message_from_person % {
                        "person": sender_name,
                        "app": self.app_name,
                    }
            else:
                # There's more than one notification for this room, so just
                # say there are several
                if room_name is not None:
                    return self.email_subjects.messages_in_room % {
                        "room": room_name,
                        "app": self.app_name,
                    }
                else:
                    # If the room doesn't have a name, say who the messages
                    # are from explicitly to avoid, "messages in the Bob room"
                    sender_ids = list({
                        notif_events[n["event_id"]].sender
                        for n in notifs_by_room[room_id]
                    })

                    member_events = await self.store.get_events([
                        room_state_ids[room_id][("m.room.member", s)]
                        for s in sender_ids
                    ])

                    return self.email_subjects.messages_from_person % {
                        "person":
                        descriptor_from_member_events(member_events.values()),
                        "app":
                        self.app_name,
                    }
        else:
            # Stuff's happened in multiple different rooms

            # ...but we still refer to the 'reason' room which triggered the mail
            if reason["room_name"] is not None:
                return self.email_subjects.messages_in_room_and_others % {
                    "room": reason["room_name"],
                    "app": self.app_name,
                }
            else:
                # If the reason room doesn't have a name, say who the messages
                # are from explicitly to avoid, "messages in the Bob room"
                room_id = reason["room_id"]

                sender_ids = list({
                    notif_events[n["event_id"]].sender
                    for n in notifs_by_room[room_id]
                })

                member_events = await self.store.get_events([
                    room_state_ids[room_id][("m.room.member", s)]
                    for s in sender_ids
                ])

                return self.email_subjects.messages_from_person_and_others % {
                    "person": descriptor_from_member_events(
                        member_events.values()),
                    "app": self.app_name,
                }
Пример #12
0
    def make_summary_text(self, notifs_by_room, state_by_room,
                          notif_events, user_id, reason):
        if len(notifs_by_room) == 1:
            # Only one room has new stuff
            room_id = notifs_by_room.keys()[0]

            # If the room has some kind of name, use it, but we don't
            # want the generated-from-names one here otherwise we'll
            # end up with, "new message from Bob in the Bob room"
            room_name = yield calculate_room_name(
                self.store, state_by_room[room_id], user_id, fallback_to_members=False
            )

            my_member_event = state_by_room[room_id][("m.room.member", user_id)]
            if my_member_event.content["membership"] == "invite":
                inviter_member_event = state_by_room[room_id][
                    ("m.room.member", my_member_event.sender)
                ]
                inviter_name = name_from_member_event(inviter_member_event)

                if room_name is None:
                    defer.returnValue(INVITE_FROM_PERSON % {
                        "person": inviter_name,
                        "app": self.app_name
                    })
                else:
                    defer.returnValue(INVITE_FROM_PERSON_TO_ROOM % {
                        "person": inviter_name,
                        "room": room_name,
                        "app": self.app_name,
                    })

            sender_name = None
            if len(notifs_by_room[room_id]) == 1:
                # There is just the one notification, so give some detail
                event = notif_events[notifs_by_room[room_id][0]["event_id"]]
                if ("m.room.member", event.sender) in state_by_room[room_id]:
                    state_event = state_by_room[room_id][("m.room.member", event.sender)]
                    sender_name = name_from_member_event(state_event)

                if sender_name is not None and room_name is not None:
                    defer.returnValue(MESSAGE_FROM_PERSON_IN_ROOM % {
                        "person": sender_name,
                        "room": room_name,
                        "app": self.app_name,
                    })
                elif sender_name is not None:
                    defer.returnValue(MESSAGE_FROM_PERSON % {
                        "person": sender_name,
                        "app": self.app_name,
                    })
            else:
                # There's more than one notification for this room, so just
                # say there are several
                if room_name is not None:
                    defer.returnValue(MESSAGES_IN_ROOM % {
                        "room": room_name,
                        "app": self.app_name,
                    })
                else:
                    # If the room doesn't have a name, say who the messages
                    # are from explicitly to avoid, "messages in the Bob room"
                    sender_ids = list(set([
                        notif_events[n['event_id']].sender
                        for n in notifs_by_room[room_id]
                    ]))

                    defer.returnValue(MESSAGES_FROM_PERSON % {
                        "person": descriptor_from_member_events([
                            state_by_room[room_id][("m.room.member", s)]
                            for s in sender_ids
                        ]),
                        "app": self.app_name,
                    })
        else:
            # Stuff's happened in multiple different rooms

            # ...but we still refer to the 'reason' room which triggered the mail
            if reason['room_name'] is not None:
                defer.returnValue(MESSAGES_IN_ROOM_AND_OTHERS % {
                    "room": reason['room_name'],
                    "app": self.app_name,
                })
            else:
                # If the reason room doesn't have a name, say who the messages
                # are from explicitly to avoid, "messages in the Bob room"
                sender_ids = list(set([
                    notif_events[n['event_id']].sender
                    for n in notifs_by_room[reason['room_id']]
                ]))

                defer.returnValue(MESSAGES_FROM_PERSON_AND_OTHERS % {
                    "person": descriptor_from_member_events([
                        state_by_room[reason['room_id']][("m.room.member", s)]
                        for s in sender_ids
                    ]),
                    "app": self.app_name,
                })