def get_room_vars(self, room_id, user_id, notifs, notif_events, room_state_ids): my_member_event_id = room_state_ids[("m.room.member", user_id)] my_member_event = yield self.store.get_event(my_member_event_id) is_invite = my_member_event.content["membership"] == "invite" room_name = yield calculate_room_name(self.store, room_state_ids, user_id) room_vars = { "title": room_name, "hash": string_ordinal_total(room_id), # See sender avatar hash "notifs": [], "invite": is_invite, "link": self.make_room_link(room_id), } if not is_invite: for n in notifs: notifvars = yield self.get_notif_vars( n, user_id, notif_events[n['event_id']], room_state_ids) # merge overlapping notifs together. # relies on the notifs being in chronological order. merge = False if room_vars['notifs'] and 'messages' in room_vars['notifs'][ -1]: prev_messages = room_vars['notifs'][-1]['messages'] for message in notifvars['messages']: pm = list( filter(lambda pm: pm['id'] == message['id'], prev_messages)) if pm: if not message["is_historical"]: pm[0]["is_historical"] = False merge = True elif merge: # we're merging, so append any remaining messages # in this notif to the previous one prev_messages.append(message) if not merge: room_vars['notifs'].append(notifvars) defer.returnValue(room_vars)
def _calculate_room_name( self, events: StateMap[dict], user_id: str = "", fallback_to_members: bool = True, fallback_to_single_member: bool = True, ): # This isn't 100% accurate, but works with MockDataStore. room_state_ids = {k[0]: k[0] for k in events} return self.get_success( calculate_room_name( MockDataStore(events), room_state_ids, user_id or self.USER_ID, fallback_to_members, fallback_to_single_member, ))
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
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)
def get_room_vars(self, room_id, user_id, notifs, notif_events, room_state_ids): my_member_event_id = room_state_ids[("m.room.member", user_id)] my_member_event = yield self.store.get_event(my_member_event_id) is_invite = my_member_event.content["membership"] == "invite" room_name = yield calculate_room_name(self.store, room_state_ids, user_id) room_vars = { "title": room_name, "hash": string_ordinal_total(room_id), # See sender avatar hash "notifs": [], "invite": is_invite, "link": self.make_room_link(room_id), } if not is_invite: for n in notifs: notifvars = yield self.get_notif_vars( n, user_id, notif_events[n['event_id']], room_state_ids ) # merge overlapping notifs together. # relies on the notifs being in chronological order. merge = False if room_vars['notifs'] and 'messages' in room_vars['notifs'][-1]: prev_messages = room_vars['notifs'][-1]['messages'] for message in notifvars['messages']: pm = list(filter(lambda pm: pm['id'] == message['id'], prev_messages)) if pm: if not message["is_historical"]: pm[0]["is_historical"] = False merge = True elif merge: # we're merging, so append any remaining messages # in this notif to the previous one prev_messages.append(message) if not merge: room_vars['notifs'].append(notifvars) defer.returnValue(room_vars)
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, })
def send_notification_mail(self, app_id, user_id, email_address, push_actions, reason): try: from_string = self.hs.config.email_notif_from % { "app": self.app_name } except TypeError: from_string = self.hs.config.email_notif_from raw_from = email.utils.parseaddr(from_string)[1] raw_to = email.utils.parseaddr(email_address)[1] if raw_to == '': raise RuntimeError("Invalid 'to' address") rooms_in_order = deduped_ordered_list( [pa['room_id'] for pa in push_actions]) notif_events = yield self.store.get_events( [pa['event_id'] for pa in push_actions]) notifs_by_room = {} for pa in push_actions: notifs_by_room.setdefault(pa["room_id"], []).append(pa) # collect the current state for all the rooms in which we have # notifications state_by_room = {} try: user_display_name = yield self.store.get_profile_displayname( UserID.from_string(user_id).localpart) if user_display_name is None: user_display_name = user_id except StoreError: user_display_name = user_id @defer.inlineCallbacks def _fetch_room_state(room_id): room_state = yield self.store.get_current_state_ids(room_id) state_by_room[room_id] = room_state # Run at most 3 of these at once: sync does 10 at a time but email # notifs are much less realtime than sync so we can afford to wait a bit. yield concurrently_execute(_fetch_room_state, rooms_in_order, 3) # 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 = [] for r in rooms_in_order: roomvars = yield self.get_room_vars(r, user_id, notifs_by_room[r], notif_events, state_by_room[r]) rooms.append(roomvars) reason['room_name'] = yield calculate_room_name( self.store, state_by_room[reason['room_id']], user_id, fallback_to_members=True) summary_text = yield self.make_summary_text(notifs_by_room, state_by_room, notif_events, user_id, reason) template_vars = { "user_display_name": user_display_name, "unsubscribe_link": self.make_unsubscribe_link(user_id, app_id, email_address), "summary_text": summary_text, "app_name": self.app_name, "rooms": rooms, "reason": reason, } html_text = self.notif_template_html.render(**template_vars) html_part = MIMEText(html_text, "html", "utf8") plain_text = self.notif_template_text.render(**template_vars) text_part = MIMEText(plain_text, "plain", "utf8") multipart_msg = MIMEMultipart('alternative') multipart_msg['Subject'] = "[%s] %s" % (self.app_name, summary_text) multipart_msg['From'] = from_string multipart_msg['To'] = email_address multipart_msg['Date'] = email.utils.formatdate() multipart_msg['Message-ID'] = email.utils.make_msgid() multipart_msg.attach(text_part) multipart_msg.attach(html_part) logger.info("Sending email push notification to %s" % email_address) # logger.debug(html_text) yield sendmail( self.hs.config.email_smtp_host, raw_from, raw_to, multipart_msg.as_string(), port=self.hs.config.email_smtp_port, requireAuthentication=self.hs.config.email_smtp_user is not None, username=self.hs.config.email_smtp_user, password=self.hs.config.email_smtp_pass, requireTransportSecurity=self.hs.config.require_transport_security)
def send_notification_mail( self, app_id, user_id, email_address, push_actions, reason ): """Send email regarding a user's room notifications""" rooms_in_order = deduped_ordered_list([pa["room_id"] for pa in push_actions]) notif_events = yield self.store.get_events( [pa["event_id"] for pa in push_actions] ) notifs_by_room = {} for pa in push_actions: notifs_by_room.setdefault(pa["room_id"], []).append(pa) # collect the current state for all the rooms in which we have # notifications state_by_room = {} try: user_display_name = yield self.store.get_profile_displayname( UserID.from_string(user_id).localpart ) if user_display_name is None: user_display_name = user_id except StoreError: user_display_name = user_id @defer.inlineCallbacks def _fetch_room_state(room_id): room_state = yield self.store.get_current_state_ids(room_id) state_by_room[room_id] = room_state # Run at most 3 of these at once: sync does 10 at a time but email # notifs are much less realtime than sync so we can afford to wait a bit. yield concurrently_execute(_fetch_room_state, rooms_in_order, 3) # 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 = [] for r in rooms_in_order: roomvars = yield self.get_room_vars( r, user_id, notifs_by_room[r], notif_events, state_by_room[r] ) rooms.append(roomvars) reason["room_name"] = yield calculate_room_name( self.store, state_by_room[reason["room_id"]], user_id, fallback_to_members=True, ) summary_text = yield self.make_summary_text( notifs_by_room, state_by_room, notif_events, user_id, reason ) template_vars = { "user_display_name": user_display_name, "unsubscribe_link": self.make_unsubscribe_link( user_id, app_id, email_address ), "summary_text": summary_text, "app_name": self.app_name, "rooms": rooms, "reason": reason, } yield self.send_email( email_address, "[%s] %s" % (self.app_name, summary_text), template_vars )
def send_notification_mail(self, app_id, user_id, email_address, push_actions, reason): try: from_string = self.hs.config.email_notif_from % { "app": self.app_name } except TypeError: from_string = self.hs.config.email_notif_from raw_from = email.utils.parseaddr(from_string)[1] raw_to = email.utils.parseaddr(email_address)[1] if raw_to == '': raise RuntimeError("Invalid 'to' address") rooms_in_order = deduped_ordered_list( [pa['room_id'] for pa in push_actions] ) notif_events = yield self.store.get_events( [pa['event_id'] for pa in push_actions] ) notifs_by_room = {} for pa in push_actions: notifs_by_room.setdefault(pa["room_id"], []).append(pa) # collect the current state for all the rooms in which we have # notifications state_by_room = {} try: user_display_name = yield self.store.get_profile_displayname( UserID.from_string(user_id).localpart ) if user_display_name is None: user_display_name = user_id except StoreError: user_display_name = user_id @defer.inlineCallbacks def _fetch_room_state(room_id): room_state = yield self.state_handler.get_current_state_ids(room_id) state_by_room[room_id] = room_state # Run at most 3 of these at once: sync does 10 at a time but email # notifs are much less realtime than sync so we can afford to wait a bit. yield concurrently_execute(_fetch_room_state, rooms_in_order, 3) # 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 = [] for r in rooms_in_order: roomvars = yield self.get_room_vars( r, user_id, notifs_by_room[r], notif_events, state_by_room[r] ) rooms.append(roomvars) reason['room_name'] = yield calculate_room_name( self.store, state_by_room[reason['room_id']], user_id, fallback_to_members=True ) summary_text = yield self.make_summary_text( notifs_by_room, state_by_room, notif_events, user_id, reason ) template_vars = { "user_display_name": user_display_name, "unsubscribe_link": self.make_unsubscribe_link( user_id, app_id, email_address ), "summary_text": summary_text, "app_name": self.app_name, "rooms": rooms, "reason": reason, } html_text = self.notif_template_html.render(**template_vars) html_part = MIMEText(html_text, "html", "utf8") plain_text = self.notif_template_text.render(**template_vars) text_part = MIMEText(plain_text, "plain", "utf8") multipart_msg = MIMEMultipart('alternative') multipart_msg['Subject'] = "[%s] %s" % (self.app_name, summary_text) multipart_msg['From'] = from_string multipart_msg['To'] = email_address multipart_msg['Date'] = email.utils.formatdate() multipart_msg['Message-ID'] = email.utils.make_msgid() multipart_msg.attach(text_part) multipart_msg.attach(html_part) logger.info("Sending email push notification to %s" % email_address) # logger.debug(html_text) yield sendmail( self.hs.config.email_smtp_host, raw_from, raw_to, multipart_msg.as_string(), port=self.hs.config.email_smtp_port )
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, })