def handle_missedmessage_emails( user_profile_id: int, missed_email_events: Iterable[Dict[str, Any]]) -> None: message_ids = [event.get('message_id') for event in missed_email_events] user_profile = get_user_profile_by_id(user_profile_id) if not receives_offline_email_notifications(user_profile): return messages = Message.objects.filter( usermessage__user_profile_id=user_profile, id__in=message_ids, usermessage__flags=~UserMessage.flags.read) # Cancel missed-message emails for deleted messages messages = [um for um in messages if um.content != "(deleted)"] if not messages: return messages_by_recipient_subject = defaultdict( list) # type: Dict[Tuple[int, str], List[Message]] for msg in messages: if msg.recipient.type == Recipient.PERSONAL: # For PM's group using (recipient, sender). messages_by_recipient_subject[(msg.recipient_id, msg.sender_id)].append(msg) else: messages_by_recipient_subject[(msg.recipient_id, msg.topic_name())].append(msg) message_count_by_recipient_subject = { recipient_subject: len(msgs) for recipient_subject, msgs in messages_by_recipient_subject.items() } for msg_list in messages_by_recipient_subject.values(): msg = min(msg_list, key=lambda msg: msg.pub_date) if msg.is_stream_message(): msg_list.extend(get_context_for_message(msg)) # Sort emails by least recently-active discussion. recipient_subjects = [] # type: List[Tuple[Tuple[int, str], int]] for recipient_subject, msg_list in messages_by_recipient_subject.items(): max_message_id = max(msg_list, key=lambda msg: msg.id).id recipient_subjects.append((recipient_subject, max_message_id)) recipient_subjects = sorted(recipient_subjects, key=lambda x: x[1]) # Send an email per recipient subject pair for recipient_subject, _ in recipient_subjects: unique_messages = { m.id: m for m in messages_by_recipient_subject[recipient_subject] } do_send_missedmessage_events_reply_in_zulip( user_profile, list(unique_messages.values()), message_count_by_recipient_subject[recipient_subject], )
def handle_missedmessage_emails(user_profile_id: int, missed_email_events: Iterable[Dict[str, Any]]) -> None: message_ids = {event.get('message_id'): event.get('trigger') for event in missed_email_events} user_profile = get_user_profile_by_id(user_profile_id) if not receives_offline_email_notifications(user_profile): return messages = Message.objects.filter(usermessage__user_profile_id=user_profile, id__in=message_ids, usermessage__flags=~UserMessage.flags.read) # Cancel missed-message emails for deleted messages messages = [um for um in messages if um.content != "(deleted)"] if not messages: return messages_by_recipient_subject = defaultdict(list) # type: Dict[Tuple[int, str], List[Message]] for msg in messages: if msg.recipient.type == Recipient.PERSONAL: # For PM's group using (recipient, sender). messages_by_recipient_subject[(msg.recipient_id, msg.sender_id)].append(msg) else: messages_by_recipient_subject[(msg.recipient_id, msg.topic_name())].append(msg) message_count_by_recipient_subject = { recipient_subject: len(msgs) for recipient_subject, msgs in messages_by_recipient_subject.items() } for msg_list in messages_by_recipient_subject.values(): msg = min(msg_list, key=lambda msg: msg.pub_date) if msg.is_stream_message(): context_messages = get_context_for_message(msg) filtered_context_messages = bulk_access_messages(user_profile, context_messages) msg_list.extend(filtered_context_messages) # Sort emails by least recently-active discussion. recipient_subjects = [] # type: List[Tuple[Tuple[int, str], int]] for recipient_subject, msg_list in messages_by_recipient_subject.items(): max_message_id = max(msg_list, key=lambda msg: msg.id).id recipient_subjects.append((recipient_subject, max_message_id)) recipient_subjects = sorted(recipient_subjects, key=lambda x: x[1]) # Send an email per recipient subject pair for recipient_subject, ignored_max_id in recipient_subjects: unique_messages = {} for m in messages_by_recipient_subject[recipient_subject]: unique_messages[m.id] = dict( message=m, trigger=message_ids.get(m.id) ) do_send_missedmessage_events_reply_in_zulip( user_profile, list(unique_messages.values()), message_count_by_recipient_subject[recipient_subject], )
def handle_missedmessage_emails(user_profile_id, missed_email_events): # type: (int, Iterable[Dict[str, Any]]) -> None message_ids = [event.get('message_id') for event in missed_email_events] user_profile = get_user_profile_by_id(user_profile_id) if not receives_offline_notifications(user_profile): return messages = [ um.message for um in UserMessage.objects.filter(user_profile=user_profile, message__id__in=message_ids, flags=~UserMessage.flags.read) ] if not messages: return messages_by_recipient_subject = defaultdict( list) # type: Dict[Tuple[int, str], List[Message]] for msg in messages: messages_by_recipient_subject[(msg.recipient_id, msg.subject)].append(msg) mesage_count_by_recipient_subject = { recipient_subject: len(msgs) for recipient_subject, msgs in messages_by_recipient_subject.items() } for msg_list in messages_by_recipient_subject.values(): msg = min(msg_list, key=lambda msg: msg.pub_date) if msg.recipient.type == Recipient.STREAM: msg_list.extend(get_context_for_message(msg)) # Send an email per recipient subject pair if user_profile.realm.domain == 'zulip.com': for recipient_subject, msg_list in messages_by_recipient_subject.items( ): unique_messages = {m.id: m for m in msg_list} do_send_missedmessage_events_reply_in_zulip( user_profile, list(unique_messages.values()), mesage_count_by_recipient_subject[recipient_subject], ) else: all_messages = [ msg_ for msg_list in messages_by_recipient_subject.values() for msg_ in msg_list ] unique_messages = {m.id: m for m in all_messages} do_send_missedmessage_events( user_profile, list(unique_messages.values()), len(messages), )
def handle_missedmessage_emails(user_profile_id, missed_email_events): # type: (int, Iterable[Dict[str, Any]]) -> None message_ids = [event.get('message_id') for event in missed_email_events] user_profile = get_user_profile_by_id(user_profile_id) if not receives_offline_notifications(user_profile): return messages = [um.message for um in UserMessage.objects.filter(user_profile=user_profile, message__id__in=message_ids, flags=~UserMessage.flags.read)] if not messages: return messages_by_recipient_subject = defaultdict(list) # type: Dict[Tuple[int, str], List[Message]] for msg in messages: messages_by_recipient_subject[(msg.recipient_id, msg.subject)].append(msg) mesage_count_by_recipient_subject = { recipient_subject: len(msgs) for recipient_subject, msgs in messages_by_recipient_subject.items() } for msg_list in messages_by_recipient_subject.values(): msg = min(msg_list, key=lambda msg: msg.pub_date) if msg.recipient.type == Recipient.STREAM: msg_list.extend(get_context_for_message(msg)) # Send an email per recipient subject pair if user_profile.realm.domain == 'zulip.com': for recipient_subject, msg_list in messages_by_recipient_subject.items(): unique_messages = {m.id: m for m in msg_list} do_send_missedmessage_events_reply_in_zulip( user_profile, list(unique_messages.values()), mesage_count_by_recipient_subject[recipient_subject], ) else: all_messages = [ msg_ for msg_list in messages_by_recipient_subject.values() for msg_ in msg_list ] unique_messages = {m.id: m for m in all_messages} do_send_missedmessage_events( user_profile, list(unique_messages.values()), len(messages), )
def handle_missedmessage_emails(user_profile_id: int, missed_email_events: Iterable[Dict[str, Any]]) -> None: message_ids = [event.get('message_id') for event in missed_email_events] user_profile = get_user_profile_by_id(user_profile_id) if not receives_offline_email_notifications(user_profile): return messages = Message.objects.filter(usermessage__user_profile_id=user_profile, id__in=message_ids, usermessage__flags=~UserMessage.flags.read) # Cancel missed-message emails for deleted messages messages = [um for um in messages if um.content != "(deleted)"] if not messages: return messages_by_recipient_subject = defaultdict(list) # type: Dict[Tuple[int, Text], List[Message]] for msg in messages: if msg.recipient.type == Recipient.PERSONAL: # For PM's group using (recipient, sender). messages_by_recipient_subject[(msg.recipient_id, msg.sender_id)].append(msg) else: messages_by_recipient_subject[(msg.recipient_id, msg.topic_name())].append(msg) message_count_by_recipient_subject = { recipient_subject: len(msgs) for recipient_subject, msgs in messages_by_recipient_subject.items() } for msg_list in messages_by_recipient_subject.values(): msg = min(msg_list, key=lambda msg: msg.pub_date) if msg.is_stream_message(): msg_list.extend(get_context_for_message(msg)) # Send an email per recipient subject pair for recipient_subject, msg_list in messages_by_recipient_subject.items(): unique_messages = {m.id: m for m in msg_list} do_send_missedmessage_events_reply_in_zulip( user_profile, list(unique_messages.values()), message_count_by_recipient_subject[recipient_subject], )
def handle_missedmessage_emails(user_profile_id, missed_email_events): # type: (int, Iterable[Dict[str, Any]]) -> None message_ids = [event.get('message_id') for event in missed_email_events] user_profile = get_user_profile_by_id(user_profile_id) if not receives_offline_notifications(user_profile): return messages = Message.objects.filter( usermessage__user_profile_id=user_profile, id__in=message_ids, usermessage__flags=~UserMessage.flags.read) # Cancel missed-message emails for deleted messages messages = [um for um in messages if um.content != "(deleted)"] if not messages: return messages_by_recipient_subject = defaultdict( list) # type: Dict[Tuple[int, Text], List[Message]] for msg in messages: messages_by_recipient_subject[(msg.recipient_id, msg.topic_name())].append(msg) message_count_by_recipient_subject = { recipient_subject: len(msgs) for recipient_subject, msgs in messages_by_recipient_subject.items() } for msg_list in messages_by_recipient_subject.values(): msg = min(msg_list, key=lambda msg: msg.pub_date) if msg.recipient.type == Recipient.STREAM: msg_list.extend(get_context_for_message(msg)) # Send an email per recipient subject pair for recipient_subject, msg_list in messages_by_recipient_subject.items(): unique_messages = {m.id: m for m in msg_list} do_send_missedmessage_events_reply_in_zulip( user_profile, list(unique_messages.values()), message_count_by_recipient_subject[recipient_subject], )
def handle_missedmessage_emails( user_profile_id: int, missed_email_events: Iterable[Dict[str, Any]]) -> None: message_ids = { event.get("message_id"): event.get("trigger") for event in missed_email_events } user_profile = get_user_profile_by_id(user_profile_id) if not receives_offline_email_notifications(user_profile): return # Note: This query structure automatically filters out any # messages that were permanently deleted, since those would now be # in the ArchivedMessage table, not the Message table. messages = Message.objects.filter( usermessage__user_profile_id=user_profile, id__in=message_ids, usermessage__flags=~UserMessage.flags.read, ) # Cancel missed-message emails for deleted messages messages = [um for um in messages if um.content != "(deleted)"] if not messages: return # We bucket messages by tuples that identify similar messages. # For streams it's recipient_id and topic. # For PMs it's recipient id and sender. messages_by_bucket: Dict[Tuple[int, str], List[Message]] = defaultdict(list) for msg in messages: if msg.recipient.type == Recipient.PERSONAL: # For PM's group using (recipient, sender). messages_by_bucket[(msg.recipient_id, msg.sender_id)].append(msg) else: messages_by_bucket[(msg.recipient_id, msg.topic_name())].append(msg) message_count_by_bucket = { bucket_tup: len(msgs) for bucket_tup, msgs in messages_by_bucket.items() } for msg_list in messages_by_bucket.values(): msg = min(msg_list, key=lambda msg: msg.date_sent) if msg.is_stream_message(): context_messages = get_context_for_message(msg) filtered_context_messages = bulk_access_messages( user_profile, context_messages) msg_list.extend(filtered_context_messages) # Sort emails by least recently-active discussion. bucket_tups: List[Tuple[Tuple[int, str], int]] = [] for bucket_tup, msg_list in messages_by_bucket.items(): max_message_id = max(msg_list, key=lambda msg: msg.id).id bucket_tups.append((bucket_tup, max_message_id)) bucket_tups = sorted(bucket_tups, key=lambda x: x[1]) # Send an email per bucket. for bucket_tup, ignored_max_id in bucket_tups: unique_messages = {} for m in messages_by_bucket[bucket_tup]: unique_messages[m.id] = dict( message=m, trigger=message_ids.get(m.id), ) do_send_missedmessage_events_reply_in_zulip( user_profile, list(unique_messages.values()), message_count_by_bucket[bucket_tup], )
def handle_missedmessage_emails( user_profile_id: int, missed_email_events: Iterable[Dict[str, Any]]) -> None: message_ids = { event.get("message_id"): { "trigger": event.get("trigger"), "mentioned_user_group_id": event.get("mentioned_user_group_id"), } for event in missed_email_events } user_profile = get_user_profile_by_id(user_profile_id) if user_profile.is_bot: # nocoverage # We don't expect to reach here for bot users. However, this code exists # to find and throw away any pre-existing events in the queue while # upgrading from versions before our notifiability logic was implemented. # TODO/compatibility: This block can be removed when one can no longer # upgrade from versions <= 4.0 to versions >= 5.0 logger.warning("Send-email event found for bot user %s. Skipping.", user_profile_id) return if not user_profile.enable_offline_email_notifications: # BUG: Investigate why it's possible to get here. return # nocoverage # Note: This query structure automatically filters out any # messages that were permanently deleted, since those would now be # in the ArchivedMessage table, not the Message table. messages = Message.objects.filter( usermessage__user_profile_id=user_profile, id__in=message_ids, usermessage__flags=~UserMessage.flags.read, ) # Cancel missed-message emails for deleted messages messages = [um for um in messages if um.content != "(deleted)"] if not messages: return # We bucket messages by tuples that identify similar messages. # For streams it's recipient_id and topic. # For PMs it's recipient id and sender. messages_by_bucket: Dict[Tuple[int, str], List[Message]] = defaultdict(list) for msg in messages: if msg.recipient.type == Recipient.PERSONAL: # For PM's group using (recipient, sender). messages_by_bucket[(msg.recipient_id, msg.sender_id)].append(msg) else: messages_by_bucket[(msg.recipient_id, msg.topic_name())].append(msg) message_count_by_bucket = { bucket_tup: len(msgs) for bucket_tup, msgs in messages_by_bucket.items() } for msg_list in messages_by_bucket.values(): msg = min(msg_list, key=lambda msg: msg.date_sent) if msg.is_stream_message(): context_messages = get_context_for_message(msg) filtered_context_messages = bulk_access_messages( user_profile, context_messages) msg_list.extend(filtered_context_messages) # Sort emails by least recently-active discussion. bucket_tups: List[Tuple[Tuple[int, str], int]] = [] for bucket_tup, msg_list in messages_by_bucket.items(): max_message_id = max(msg_list, key=lambda msg: msg.id).id bucket_tups.append((bucket_tup, max_message_id)) bucket_tups = sorted(bucket_tups, key=lambda x: x[1]) # Send an email per bucket. for bucket_tup, ignored_max_id in bucket_tups: unique_messages = {} for m in messages_by_bucket[bucket_tup]: message_info = message_ids.get(m.id) unique_messages[m.id] = dict( message=m, trigger=message_info["trigger"] if message_info else None, mentioned_user_group_id=message_info.get( "mentioned_user_group_id") if message_info is not None else None, ) do_send_missedmessage_events_reply_in_zulip( user_profile, list(unique_messages.values()), message_count_by_bucket[bucket_tup], )
def handle_missedmessage_emails(user_profile_id: int, missed_email_events: Iterable[Dict[str, Any]]) -> None: message_ids = {event.get('message_id'): event.get('trigger') for event in missed_email_events} user_profile = get_user_profile_by_id(user_profile_id) if not receives_offline_email_notifications(user_profile): return # Note: This query structure automatically filters out any # messages that were permanently deleted, since those would now be # in the ArchivedMessage table, not the Message table. messages = Message.objects.filter(usermessage__user_profile_id=user_profile, id__in=message_ids, usermessage__flags=~UserMessage.flags.read) # Cancel missed-message emails for deleted messages messages = [um for um in messages if um.content != "(deleted)"] if not messages: return # We bucket messages by tuples that identify similar messages. # For streams it's recipient_id and topic. # For PMs it's recipient id and sender. messages_by_bucket = defaultdict(list) # type: Dict[Tuple[int, str], List[Message]] for msg in messages: if msg.recipient.type == Recipient.PERSONAL: # For PM's group using (recipient, sender). messages_by_bucket[(msg.recipient_id, msg.sender_id)].append(msg) else: messages_by_bucket[(msg.recipient_id, msg.topic_name())].append(msg) message_count_by_bucket = { bucket_tup: len(msgs) for bucket_tup, msgs in messages_by_bucket.items() } for msg_list in messages_by_bucket.values(): msg = min(msg_list, key=lambda msg: msg.pub_date) if msg.is_stream_message(): context_messages = get_context_for_message(msg) filtered_context_messages = bulk_access_messages(user_profile, context_messages) msg_list.extend(filtered_context_messages) # Sort emails by least recently-active discussion. bucket_tups = [] # type: List[Tuple[Tuple[int, str], int]] for bucket_tup, msg_list in messages_by_bucket.items(): max_message_id = max(msg_list, key=lambda msg: msg.id).id bucket_tups.append((bucket_tup, max_message_id)) bucket_tups = sorted(bucket_tups, key=lambda x: x[1]) # Send an email per bucket. for bucket_tup, ignored_max_id in bucket_tups: unique_messages = {} for m in messages_by_bucket[bucket_tup]: unique_messages[m.id] = dict( message=m, trigger=message_ids.get(m.id) ) do_send_missedmessage_events_reply_in_zulip( user_profile, list(unique_messages.values()), message_count_by_bucket[bucket_tup], )