def get_room_vars(self, room_id, user_id, notifs, notif_events, room_state): my_member_event = room_state[("m.room.member", user_id)] is_invite = my_member_event.content["membership"] == "invite" room_vars = { "title": calculate_room_name(room_state, user_id), "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) # 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 = 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 get_room_vars(self, room_id, user_id, notifs, notif_events, room_state): my_member_event = room_state[("m.room.member", user_id)] is_invite = my_member_event.content["membership"] == "invite" room_vars = { "title": calculate_room_name(room_state, user_id), "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 ) # 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 = 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 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(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'] = calculate_room_name( state_by_room[reason['room_id']], user_id, fallback_to_members=True ) summary_text = 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 = calculate_room_name( 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: return INVITE_FROM_PERSON % { "person": inviter_name, "app": self.app_name } else: return 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: return MESSAGE_FROM_PERSON_IN_ROOM % { "person": sender_name, "room": room_name, "app": self.app_name, } elif sender_name is not None: return 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 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] ])) return 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: return 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']] ])) return 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, }
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 = calculate_room_name(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: return INVITE_FROM_PERSON % { "person": inviter_name, "app": self.app_name } else: return 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: return MESSAGE_FROM_PERSON_IN_ROOM % { "person": sender_name, "room": room_name, "app": self.app_name, } elif sender_name is not None: return 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 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] ])) return 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: return 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']] ])) return 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, }
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(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'] = calculate_room_name( state_by_room[reason['room_id']], user_id, fallback_to_members=True) summary_text = 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)