def get_apns_alert_title(message: Message) -> str: """ On an iOS notification, this is the first bolded line. """ if message.recipient.type == Recipient.HUDDLE: recipients = cast(List[Dict[str, Any]], get_display_recipient(message.recipient)) return ', '.join(sorted(r['full_name'] for r in recipients)) elif message.is_stream_message(): return "#%s > %s" % (get_display_recipient(message.recipient), message.topic_name(),) # For personal PMs, we just show the sender name. return message.sender.full_name
def get_gcm_payload(user_profile, message): # type: (UserProfile, Message) -> Dict[str, Any] content = message.content content_truncated = (len(content) > 200) if content_truncated: content = content[:200] + "..." android_data = { 'user': user_profile.email, 'event': 'message', 'alert': get_alert_from_message(message), 'zulip_message_id': message.id, # message_id is reserved for CCS 'time': datetime_to_timestamp(message.pub_date), 'content': content, 'content_truncated': content_truncated, 'sender_email': message.sender.email, 'sender_full_name': message.sender.full_name, 'sender_avatar_url': absolute_avatar_url(message.sender), } if message.recipient.type == Recipient.STREAM: android_data['recipient_type'] = "stream" android_data['stream'] = get_display_recipient(message.recipient) android_data['topic'] = message.subject elif message.recipient.type in (Recipient.HUDDLE, Recipient.PERSONAL): android_data['recipient_type'] = "private" return android_data
def send_to_missed_message_address(address, message): # type: (text_type, message.Message) -> None token = get_missed_message_token_from_address(address) key = missed_message_redis_key(token) result = redis_client.hmget(key, 'user_profile_id', 'recipient_id', 'subject') if not all(val is not None for val in result): raise ZulipEmailForwardError('Missing missed message address data') user_profile_id, recipient_id, subject = result user_profile = get_user_profile_by_id(user_profile_id) recipient = Recipient.objects.get(id=recipient_id) display_recipient = get_display_recipient(recipient) # Testing with basestring so we don't depend on the list return type from # get_display_recipient if not isinstance(display_recipient, six.string_types): recipient_str = ','.join([user['email'] for user in display_recipient]) else: recipient_str = display_recipient body = filter_footer(extract_body(message)) body += extract_and_upload_attachments(message, user_profile.realm) if not body: body = '(No email body)' if recipient.type == Recipient.STREAM: recipient_type_name = 'stream' else: recipient_type_name = 'private' internal_send_message(user_profile.email, recipient_type_name, recipient_str, subject, body)
def export_messages_single_user(user_profile, chunk_size=1000, output_dir=None): # type: (UserProfile, int, Path) -> None user_message_query = UserMessage.objects.filter(user_profile=user_profile) min_id = -1 dump_file_id = 1 while True: actual_query = user_message_query.select_related("message", "message__sending_client").filter(id__gt=min_id)[0:chunk_size] user_message_chunk = [um for um in actual_query] user_message_ids = set(um.id for um in user_message_chunk) if len(user_message_chunk) == 0: break message_chunk = [] for user_message in user_message_chunk: item = model_to_dict(user_message.message) item['flags'] = user_message.flags_list() item['flags_mask'] = user_message.flags.mask # Add a few nice, human-readable details item['sending_client_name'] = user_message.message.sending_client.name item['display_recipient'] = get_display_recipient(user_message.message.recipient) message_chunk.append(item) message_filename = os.path.join(output_dir, "messages-%06d.json" % (dump_file_id,)) logging.info("Fetched Messages for %s" % (message_filename,)) output = {'zerver_message': message_chunk} floatify_datetime_fields(output, 'zerver_message') write_message_export(message_filename, output) min_id = max(user_message_ids) dump_file_id += 1
def message_header(user_profile, message): # type: (UserProfile, Message) -> Dict[str, Any] disp_recipient = get_display_recipient(message.recipient) if message.recipient.type == Recipient.PERSONAL: header = u"You and %s" % (message.sender.full_name) html_link = pm_narrow_url(user_profile.realm, [message.sender.email]) header_html = u"<a style='color: #ffffff;' href='%s'>%s</a>" % (html_link, header) elif message.recipient.type == Recipient.HUDDLE: assert not isinstance(disp_recipient, text_type) other_recipients = [r['full_name'] for r in disp_recipient if r['email'] != user_profile.email] header = u"You and %s" % (", ".join(other_recipients),) html_link = pm_narrow_url(user_profile.realm, [r["email"] for r in disp_recipient if r["email"] != user_profile.email]) header_html = u"<a style='color: #ffffff;' href='%s'>%s</a>" % (html_link, header) else: assert isinstance(disp_recipient, text_type) header = u"%s > %s" % (disp_recipient, message.topic_name()) stream_link = stream_narrow_url(user_profile.realm, disp_recipient) topic_link = topic_narrow_url(user_profile.realm, disp_recipient, message.subject) header_html = u"<a href='%s'>%s</a> > <a href='%s'>%s</a>" % ( stream_link, disp_recipient, topic_link, message.subject) return {"plain": header, "html": header_html, "stream_message": message.recipient.type_name() == "stream"}
def test_receive_stream_email_messages_success(self) -> None: # build dummy messages for stream # test valid incoming stream message is processed properly user_profile = self.example_user('hamlet') self.login(user_profile.email) self.subscribe(user_profile, "Denmark") stream = get_stream("Denmark", user_profile.realm) stream_to_address = encode_email_address(stream) incoming_valid_message = MIMEText('TestStreamEmailMessages Body') # type: Any # https://github.com/python/typeshed/issues/275 incoming_valid_message['Subject'] = 'TestStreamEmailMessages Subject' incoming_valid_message['From'] = self.example_email('hamlet') incoming_valid_message['To'] = stream_to_address incoming_valid_message['Reply-to'] = self.example_email('othello') process_message(incoming_valid_message) # Hamlet is subscribed to this stream so should see the email message from Othello. message = most_recent_message(user_profile) self.assertEqual(message.content, "TestStreamEmailMessages Body") self.assertEqual(get_display_recipient(message.recipient), stream.name) self.assertEqual(message.topic_name(), incoming_valid_message['Subject'])
def test_receive_stream_email_messages_success(self): # build dummy messages for stream # test valid incoming stream message is processed properly self.login("*****@*****.**") user_profile = get_user_profile_by_email("*****@*****.**") self.subscribe_to_stream(user_profile.email, "Denmark") stream = get_stream("Denmark", user_profile.realm) stream_to_address = encode_email_address(stream) incoming_valid_message = MIMEText('TestStreamEmailMessages Body') incoming_valid_message['Subject'] = 'TestStreamEmailMessages Subject' incoming_valid_message['From'] = "*****@*****.**" incoming_valid_message['To'] = stream_to_address incoming_valid_message['Reply-to'] = "*****@*****.**" process_message(incoming_valid_message) # Hamlet is subscribed to this stream so should see the email message from Othello. message = most_recent_message(user_profile) self.assertEqual(message.content, "TestStreamEmailMessages Body") self.assertEqual(get_display_recipient(message.recipient), stream.name) self.assertEqual(message.subject, incoming_valid_message['Subject'])
def message_header(user_profile: UserProfile, message: Message) -> Dict[str, Any]: if message.recipient.type == Recipient.PERSONAL: header = "You and %s" % (message.sender.full_name,) html_link = personal_narrow_url( realm=user_profile.realm, sender=message.sender, ) header_html = "<a style='color: #ffffff;' href='%s'>%s</a>" % (html_link, header) elif message.recipient.type == Recipient.HUDDLE: disp_recipient = get_display_recipient(message.recipient) assert not isinstance(disp_recipient, str) other_recipients = [r['full_name'] for r in disp_recipient if r['id'] != user_profile.id] header = "You and %s" % (", ".join(other_recipients),) other_user_ids = [r['id'] for r in disp_recipient if r['id'] != user_profile.id] html_link = huddle_narrow_url( realm=user_profile.realm, other_user_ids=other_user_ids, ) header_html = "<a style='color: #ffffff;' href='%s'>%s</a>" % (html_link, header) else: stream = Stream.objects.only('id', 'name').get(id=message.recipient.type_id) header = "%s > %s" % (stream.name, message.topic_name()) stream_link = stream_narrow_url(user_profile.realm, stream) topic_link = topic_narrow_url(user_profile.realm, stream, message.topic_name()) header_html = "<a href='%s'>%s</a> > <a href='%s'>%s</a>" % ( stream_link, stream.name, topic_link, message.topic_name()) return {"plain": header, "html": header_html, "stream_message": message.recipient.type_name() == "stream"}
def send_json_payload(self, user_profile: UserProfile, url: str, payload: Union[str, Dict[str, Any]], stream_name: Optional[str]=None, **post_params: Any) -> Message: if stream_name is not None: self.subscribe(user_profile, stream_name) prior_msg = self.get_last_message() result = self.client_post(url, payload, **post_params) self.assert_json_success(result) # Check the correct message was sent msg = self.get_last_message() if msg.id == prior_msg.id: raise Exception(''' Your test code called an endpoint that did not write any new messages. It is probably broken (but still returns 200 due to exception handling). ''') # nocoverage self.assertEqual(msg.sender.email, user_profile.email) if stream_name is not None: self.assertEqual(get_display_recipient(msg.recipient), stream_name) # TODO: should also validate recipient for private messages return msg
def send_to_missed_message_address(address, message): # type: (text_type, message.Message) -> None token = get_missed_message_token_from_address(address) key = missed_message_redis_key(token) result = redis_client.hmget(key, "user_profile_id", "recipient_id", "subject") if not all(val is not None for val in result): raise ZulipEmailForwardError("Missing missed message address data") user_profile_id, recipient_id, subject = result user_profile = get_user_profile_by_id(user_profile_id) recipient = Recipient.objects.get(id=recipient_id) display_recipient = get_display_recipient(recipient) # Testing with basestring so we don't depend on the list return type from # get_display_recipient if not isinstance(display_recipient, six.string_types): recipient_str = ",".join([user["email"] for user in display_recipient]) else: recipient_str = display_recipient body = filter_footer(extract_body(message)) body += extract_and_upload_attachments(message, user_profile.realm) if not body: body = "(No email body)" if recipient.type == Recipient.STREAM: recipient_type_name = "stream" else: recipient_type_name = "private" internal_send_message(user_profile.email, recipient_type_name, recipient_str, subject, body) logging.info("Successfully processed email from %s to %s" % (user_profile.email, recipient_str))
def do_send_missedmessage_events(user_profile, missed_messages, message_count): """ Send a reminder email and/or push notifications to a user if she's missed some PMs by being offline `user_profile` is the user to send the reminder to `missed_messages` is a list of Message objects to remind about """ # Disabled missedmessage emails internally if not user_profile.enable_offline_email_notifications: return senders = set(m.sender.full_name for m in missed_messages) sender_str = ", ".join(senders) plural_messages = 's' if len(missed_messages) > 1 else '' template_payload = {'name': user_profile.full_name, 'messages': build_message_list(user_profile, missed_messages), 'message_count': message_count, 'url': 'https://%s' % (settings.EXTERNAL_HOST,), 'reply_warning': False, 'external_host': settings.EXTERNAL_HOST} headers = {} if all(msg.recipient.type in (Recipient.HUDDLE, Recipient.PERSONAL) for msg in missed_messages): # If we have one huddle, set a reply-to to all of the members # of the huddle except the user herself disp_recipients = [", ".join(recipient['email'] for recipient in get_display_recipient(mesg.recipient) if recipient['email'] != user_profile.email) for mesg in missed_messages] if all(msg.recipient.type == Recipient.HUDDLE for msg in missed_messages) and \ len(set(disp_recipients)) == 1: headers['Reply-To'] = disp_recipients[0] elif len(senders) == 1: headers['Reply-To'] = missed_messages[0].sender.email else: template_payload['reply_warning'] = True else: # There are some @-mentions mixed in with personals template_payload['mention'] = True template_payload['reply_warning'] = True headers['Reply-To'] = "Nobody <%s>" % (settings.NOREPLY_EMAIL_ADDRESS,) # Give users a one-click unsubscribe link they can use to stop getting # missed message emails without having to log in first. unsubscribe_link = one_click_unsubscribe_link(user_profile, "missed_messages") template_payload["unsubscribe_link"] = unsubscribe_link subject = "Missed Zulip%s from %s" % (plural_messages, sender_str) from_email = "%s (via Zulip) <%s>" % (sender_str, settings.NOREPLY_EMAIL_ADDRESS) text_content = loader.render_to_string('zerver/missed_message_email.txt', template_payload) html_content = loader.render_to_string('zerver/missed_message_email_html.txt', template_payload) msg = EmailMultiAlternatives(subject, text_content, from_email, [user_profile.email], headers = headers) msg.attach_alternative(html_content, "text/html") msg.send() user_profile.last_reminder = datetime.datetime.now() user_profile.save(update_fields=['last_reminder'])
def send_to_missed_message_address(address, message): # type: (Text, message.Message) -> None token = get_missed_message_token_from_address(address) key = missed_message_redis_key(token) result = redis_client.hmget(key, 'user_profile_id', 'recipient_id', 'subject') if not all(val is not None for val in result): raise ZulipEmailForwardError('Missing missed message address data') user_profile_id, recipient_id, subject_b = result # type: (bytes, bytes, bytes) user_profile = get_user_profile_by_id(user_profile_id) recipient = Recipient.objects.get(id=recipient_id) display_recipient = get_display_recipient(recipient) # Testing with basestring so we don't depend on the list return type from # get_display_recipient if not isinstance(display_recipient, str): recipient_str = u','.join([user['email'] for user in display_recipient]) else: recipient_str = display_recipient body = construct_zulip_body(message, user_profile.realm) if recipient.type == Recipient.STREAM: recipient_type_name = 'stream' else: recipient_type_name = 'private' internal_send_message(user_profile.realm, user_profile.email, recipient_type_name, recipient_str, subject_b.decode('utf-8'), body) logger.info("Successfully processed email from %s to %s" % ( user_profile.email, recipient_str))
def test_receive_stream_email_multiple_recipient_success(self) -> None: user_profile = self.example_user('hamlet') self.login(user_profile.email) self.subscribe(user_profile, "Denmark") stream = get_stream("Denmark", user_profile.realm) # stream address is angle-addr within multiple addresses stream_to_addresses = ["A.N. Other <*****@*****.**>", "Denmark <{}>".format(encode_email_address(stream))] incoming_valid_message = MIMEText('TestStreamEmailMessages Body') incoming_valid_message['Subject'] = 'TestStreamEmailMessages Subject' incoming_valid_message['From'] = self.example_email('hamlet') incoming_valid_message['To'] = ", ".join(stream_to_addresses) incoming_valid_message['Reply-to'] = self.example_email('othello') process_message(incoming_valid_message) # Hamlet is subscribed to this stream so should see the email message from Othello. message = most_recent_message(user_profile) self.assertEqual(message.content, "TestStreamEmailMessages Body") self.assertEqual(get_display_recipient(message.recipient), stream.name) self.assertEqual(message.topic_name(), incoming_valid_message['Subject'])
def get_streams(self, email: str, realm: Realm) -> List[str]: """ Helper function to get the stream names for a user """ user_profile = get_user(email, realm) subs = get_stream_subscriptions_for_user(user_profile).filter( active=True, ) return [cast(str, get_display_recipient(sub.recipient)) for sub in subs]
def test_stream_message_to_embedded_bot(self) -> None: self.send_stream_message(self.user_profile.email, "Denmark", content="@**{}** foo".format(self.bot_profile.full_name), topic_name="bar") last_message = self.get_last_message() self.assertEqual(last_message.content, "beep boop") self.assertEqual(last_message.sender_id, self.bot_profile.id) self.assertEqual(last_message.subject, "bar") display_recipient = get_display_recipient(last_message.recipient) self.assertEqual(display_recipient, "Denmark")
def test_stream_message_to_outgoing_webhook_bot(self, mock_requests_request: mock.Mock) -> None: self.send_stream_message(self.user_profile.email, "Denmark", content="@**{}** foo".format(self.bot_profile.full_name), topic_name="bar") last_message = self.get_last_message() self.assertEqual(last_message.content, "Success! Hidley ho, I'm a webhook responding!") self.assertEqual(last_message.sender_id, self.bot_profile.id) self.assertEqual(last_message.subject, "bar") display_recipient = get_display_recipient(last_message.recipient) self.assertEqual(display_recipient, "Denmark")
def test_pm_to_outgoing_webhook_bot(self, mock_requests_request: mock.Mock) -> None: self.send_personal_message(self.user_profile.email, self.bot_profile.email, content="foo") last_message = self.get_last_message() self.assertEqual(last_message.content, "Success! Hidley ho, I'm a webhook responding!") self.assertEqual(last_message.sender_id, self.bot_profile.id) display_recipient = get_display_recipient(last_message.recipient) # The next two lines error on mypy because the display_recipient is of type Union[Text, List[Dict[str, Any]]]. # In this case, we know that display_recipient will be of type List[Dict[str, Any]]. # Otherwise this test will error, which is wanted behavior anyway. self.assert_length(display_recipient, 1) # type: ignore self.assertEqual(display_recipient[0]['email'], self.user_profile.email) # type: ignore
def test_pm_to_embedded_bot(self) -> None: self.send_personal_message(self.user_profile.email, self.bot_profile.email, content="help") last_message = self.get_last_message() self.assertEqual(last_message.content, "beep boop") self.assertEqual(last_message.sender_id, self.bot_profile.id) display_recipient = get_display_recipient(last_message.recipient) # The next two lines error on mypy because the display_recipient is of type Union[Text, List[Dict[str, Any]]]. # In this case, we know that display_recipient will be of type List[Dict[str, Any]]. # Otherwise this test will error, which is wanted behavior anyway. self.assert_length(display_recipient, 1) # type: ignore self.assertEqual(display_recipient[0]['email'], self.user_profile.email) # type: ignore
def get_gcm_alert(message: Message) -> str: """ Determine what alert string to display based on the missed messages. """ sender_str = message.sender.full_name if message.recipient.type == Recipient.HUDDLE and message.trigger == 'private_message': return "New private group message from %s" % (sender_str,) elif message.recipient.type == Recipient.PERSONAL and message.trigger == 'private_message': return "New private message from %s" % (sender_str,) elif message.is_stream_message() and message.trigger == 'mentioned': return "New mention from %s" % (sender_str,) else: # message.is_stream_message() and message.trigger == 'stream_push_notify' return "New stream message from %s in %s" % (sender_str, get_display_recipient(message.recipient),)
def send_to_missed_message_address(address: str, message: message.Message) -> None: token = get_missed_message_token_from_address(address) key = missed_message_redis_key(token) result = redis_client.hmget(key, 'user_profile_id', 'recipient_id', 'subject') if not all(val is not None for val in result): raise ZulipEmailForwardError('Missing missed message address data') user_profile_id, recipient_id, subject_b = result # type: (bytes, bytes, bytes) user_profile = get_user_profile_by_id(user_profile_id) recipient = Recipient.objects.get(id=recipient_id) body = construct_zulip_body(message, user_profile.realm) if recipient.type == Recipient.STREAM: stream = get_stream_by_id_in_realm(recipient.type_id, user_profile.realm) internal_send_stream_message( user_profile.realm, user_profile, stream, subject_b.decode('utf-8'), body ) recipient_str = stream.name elif recipient.type == Recipient.PERSONAL: display_recipient = get_display_recipient(recipient) assert not isinstance(display_recipient, str) recipient_str = display_recipient[0]['email'] recipient_user = get_user(recipient_str, user_profile.realm) internal_send_private_message(user_profile.realm, user_profile, recipient_user, body) elif recipient.type == Recipient.HUDDLE: display_recipient = get_display_recipient(recipient) assert not isinstance(display_recipient, str) emails = [user_dict['email'] for user_dict in display_recipient] recipient_str = ', '.join(emails) internal_send_huddle_message(user_profile.realm, user_profile, emails, body) else: raise AssertionError("Invalid recipient type!") logger.info("Successfully processed email from %s to %s" % ( user_profile.email, recipient_str))
def test_recipient_for_user_ids(self) -> None: hamlet = self.example_user('hamlet') othello = self.example_user('othello') cross_realm_bot = self.example_user('welcome_bot') sender = self.example_user('iago') recipient_user_ids = [hamlet.id, othello.id, cross_realm_bot.id] result = recipient_for_user_ids(recipient_user_ids, sender) recipient = get_display_recipient(result) recipient_ids = [recipient[0]['id'], recipient[1]['id'], # type: ignore recipient[2]['id'], recipient[3]['id']] # type: ignore expected_recipient_ids = [hamlet.id, othello.id, sender.id, cross_realm_bot.id] self.assertEqual(set(recipient_ids), set(expected_recipient_ids))
def get_message_payload(message: Message) -> Dict[str, Any]: '''Common fields for `message` payloads, for all platforms.''' data = get_base_payload(message.sender.realm) # `sender_id` is preferred, but some existing versions use `sender_email`. data['sender_id'] = message.sender.id data['sender_email'] = message.sender.email if message.recipient.type == Recipient.STREAM: data['recipient_type'] = "stream" data['stream'] = get_display_recipient(message.recipient) data['topic'] = message.topic_name() elif message.recipient.type == Recipient.HUDDLE: data['recipient_type'] = "private" data['pm_users'] = huddle_users(message.recipient.id) else: # Recipient.PERSONAL data['recipient_type'] = "private" return data
def get_common_payload(message: Message) -> Dict[str, Any]: data = {} # type: Dict[str, Any] # These will let the app support logging into multiple realms and servers. data['server'] = settings.EXTERNAL_HOST data['realm_id'] = message.sender.realm.id # `sender_id` is preferred, but some existing versions use `sender_email`. data['sender_id'] = message.sender.id data['sender_email'] = message.sender.email if message.is_stream_message(): data['recipient_type'] = "stream" data['stream'] = get_display_recipient(message.recipient) data['topic'] = message.subject else: data['recipient_type'] = "private" return data
def message_header(user_profile, message): disp_recipient = get_display_recipient(message.recipient) if message.recipient.type == Recipient.PERSONAL: header = "You and %s" % (message.sender.full_name) html_link = pm_narrow_url([message.sender.email]) header_html = "<a style='color: #ffffff;' href='%s'>%s</a>" % (html_link, header) elif message.recipient.type == Recipient.HUDDLE: other_recipients = [r['full_name'] for r in disp_recipient if r['email'] != user_profile.email] header = "You and %s" % (", ".join(other_recipients),) html_link = pm_narrow_url([r["email"] for r in disp_recipient if r["email"] != user_profile.email]) header_html = "<a style='color: #ffffff;' href='%s'>%s</a>" % (html_link, header) else: header = "%s > %s" % (disp_recipient, message.subject) stream_link = stream_narrow_url(disp_recipient) topic_link = topic_narrow_url(disp_recipient, message.subject) header_html = "<a href='%s'>%s</a> > <a href='%s'>%s</a>" % ( stream_link, disp_recipient, topic_link, message.subject) return {"plain": header, "html": header_html, "stream_message": message.recipient.type_name() == "stream"}
def test_receive_stream_email_messages_blank_subject_success(self) -> None: user_profile = self.example_user('hamlet') self.login(user_profile.email) self.subscribe(user_profile, "Denmark") stream = get_stream("Denmark", user_profile.realm) stream_to_address = encode_email_address(stream) incoming_valid_message = MIMEText('TestStreamEmailMessages Body') incoming_valid_message['Subject'] = '' incoming_valid_message['From'] = self.example_email('hamlet') incoming_valid_message['To'] = stream_to_address incoming_valid_message['Reply-to'] = self.example_email('othello') process_message(incoming_valid_message) # Hamlet is subscribed to this stream so should see the email message from Othello. message = most_recent_message(user_profile) self.assertEqual(message.content, "TestStreamEmailMessages Body") self.assertEqual(get_display_recipient(message.recipient), stream.name) self.assertEqual(message.topic_name(), "(no topic)")
def get_common_payload(message: Message) -> Dict[str, Any]: data = {} # type: Dict[str, Any] # These will let the app support logging into multiple realms and servers. data['server'] = settings.EXTERNAL_HOST data['realm_id'] = message.sender.realm.id data['realm_uri'] = message.sender.realm.uri # `sender_id` is preferred, but some existing versions use `sender_email`. data['sender_id'] = message.sender.id data['sender_email'] = message.sender.email if message.recipient.type == Recipient.STREAM: data['recipient_type'] = "stream" data['stream'] = get_display_recipient(message.recipient) data['topic'] = message.topic_name() elif message.recipient.type == Recipient.HUDDLE: data['recipient_type'] = "private" data['pm_users'] = huddle_users(message.recipient.id) else: # Recipient.PERSONAL data['recipient_type'] = "private" return data
def test_receive_stream_email_show_sender_success(self) -> None: user_profile = self.example_user('hamlet') self.login(user_profile.email) self.subscribe(user_profile, "Denmark") stream = get_stream("Denmark", user_profile.realm) stream_to_address = encode_email_address(stream) parts = stream_to_address.split('@') parts[0] += "+show-sender" stream_to_address = '@'.join(parts) incoming_valid_message = MIMEText('TestStreamEmailMessages Body') incoming_valid_message['Subject'] = 'TestStreamEmailMessages Subject' incoming_valid_message['From'] = self.example_email('hamlet') incoming_valid_message['To'] = stream_to_address incoming_valid_message['Reply-to'] = self.example_email('othello') process_message(incoming_valid_message) message = most_recent_message(user_profile) self.assertEqual(message.content, "From: %s\n%s" % (self.example_email('hamlet'), "TestStreamEmailMessages Body")) self.assertEqual(get_display_recipient(message.recipient), stream.name) self.assertEqual(message.topic_name(), incoming_valid_message['Subject'])
def do_send_missedmessage_events_reply_in_zulip(user_profile: UserProfile, missed_messages: List[Message], message_count: int) -> None: """ Send a reminder email to a user if she's missed some PMs by being offline. The email will have its reply to address set to a limited used email address that will send a zulip message to the correct recipient. This allows the user to respond to missed PMs, huddles, and @-mentions directly from the email. `user_profile` is the user to send the reminder to `missed_messages` is a list of Message objects to remind about they should all have the same recipient and subject """ from zerver.context_processors import common_context # Disabled missedmessage emails internally if not user_profile.enable_offline_email_notifications: return recipients = set((msg.recipient_id, msg.subject) for msg in missed_messages) if len(recipients) != 1: raise ValueError( 'All missed_messages must have the same recipient and subject %r' % recipients ) unsubscribe_link = one_click_unsubscribe_link(user_profile, "missed_messages") context = common_context(user_profile) context.update({ 'name': user_profile.full_name, 'message_count': message_count, 'mention': missed_messages[0].is_stream_message(), 'unsubscribe_link': unsubscribe_link, 'realm_name_in_notifications': user_profile.realm_name_in_notifications, 'show_message_content': user_profile.message_content_in_email_notifications, }) # If this setting (email mirroring integration) is enabled, only then # can users reply to email to send message to Zulip. Thus, one must # ensure to display warning in the template. if settings.EMAIL_GATEWAY_PATTERN: context.update({ 'reply_warning': False, 'reply_to_zulip': True, }) else: context.update({ 'reply_warning': True, 'reply_to_zulip': False, }) from zerver.lib.email_mirror import create_missed_message_address reply_to_address = create_missed_message_address(user_profile, missed_messages[0]) if reply_to_address == FromAddress.NOREPLY: reply_to_name = None else: reply_to_name = "Zulip" senders = list(set(m.sender for m in missed_messages)) if (missed_messages[0].recipient.type == Recipient.HUDDLE): display_recipient = get_display_recipient(missed_messages[0].recipient) # Make sure that this is a list of strings, not a string. assert not isinstance(display_recipient, str) other_recipients = [r['full_name'] for r in display_recipient if r['id'] != user_profile.id] context.update({'group_pm': True}) if len(other_recipients) == 2: huddle_display_name = "%s" % (" and ".join(other_recipients)) context.update({'huddle_display_name': huddle_display_name}) elif len(other_recipients) == 3: huddle_display_name = "%s, %s, and %s" % ( other_recipients[0], other_recipients[1], other_recipients[2]) context.update({'huddle_display_name': huddle_display_name}) else: huddle_display_name = "%s, and %s others" % ( ', '.join(other_recipients[:2]), len(other_recipients) - 2) context.update({'huddle_display_name': huddle_display_name}) elif (missed_messages[0].recipient.type == Recipient.PERSONAL): context.update({'private_message': True}) else: # Keep only the senders who actually mentioned the user # # TODO: When we add wildcard mentions that send emails, add # them to the filter here. senders = list(set(m.sender for m in missed_messages if UserMessage.objects.filter(message=m, user_profile=user_profile, flags=UserMessage.flags.mentioned).exists())) context.update({'at_mention': True}) # If message content is disabled, then flush all information we pass to email. if not user_profile.message_content_in_email_notifications: context.update({ 'reply_to_zulip': False, 'messages': [], 'sender_str': "", 'realm_str': user_profile.realm.name, 'huddle_display_name': "", }) else: context.update({ 'messages': build_message_list(user_profile, missed_messages), 'sender_str': ", ".join(sender.full_name for sender in senders), 'realm_str': user_profile.realm.name, }) from_name = "Zulip missed messages" # type: str from_address = FromAddress.NOREPLY if len(senders) == 1 and settings.SEND_MISSED_MESSAGE_EMAILS_AS_USER: # If this setting is enabled, you can reply to the Zulip # missed message emails directly back to the original sender. # However, one must ensure the Zulip server is in the SPF # record for the domain, or there will be spam/deliverability # problems. sender = senders[0] from_name, from_address = (sender.full_name, sender.email) context.update({ 'reply_warning': False, 'reply_to_zulip': False, }) email_dict = { 'template_prefix': 'zerver/emails/missed_message', 'to_user_id': user_profile.id, 'from_name': from_name, 'from_address': from_address, 'reply_to_email': formataddr((reply_to_name, reply_to_address)), 'context': context} queue_json_publish("email_senders", email_dict) user_profile.last_reminder = timezone_now() user_profile.save(update_fields=['last_reminder'])
def do_send_missedmessage_events(user_profile, missed_messages, message_count): # type: (UserProfile, List[Message], int) -> None """ Send a reminder email and/or push notifications to a user if she's missed some PMs by being offline `user_profile` is the user to send the reminder to `missed_messages` is a list of Message objects to remind about """ # Disabled missedmessage emails internally if not user_profile.enable_offline_email_notifications: return senders = set(m.sender.full_name for m in missed_messages) sender_str = ", ".join(senders) plural_messages = 's' if len(missed_messages) > 1 else '' template_payload = { 'name': user_profile.full_name, 'messages': build_message_list(user_profile, missed_messages), 'message_count': message_count, 'url': 'https://%s' % (settings.EXTERNAL_HOST, ), 'reply_warning': False, 'external_host': settings.EXTERNAL_HOST } headers = {} if all(msg.recipient.type in (Recipient.HUDDLE, Recipient.PERSONAL) for msg in missed_messages): # If we have one huddle, set a reply-to to all of the members # of the huddle except the user herself disp_recipients = [ ", ".join(recipient['email'] for recipient in cast( Sequence[Mapping[str, Any]], get_display_recipient(mesg.recipient)) if recipient['email'] != user_profile.email) for mesg in missed_messages ] if all(msg.recipient.type == Recipient.HUDDLE for msg in missed_messages) and \ len(set(disp_recipients)) == 1: headers['Reply-To'] = disp_recipients[0] elif len(senders) == 1: headers['Reply-To'] = missed_messages[0].sender.email else: template_payload['reply_warning'] = True else: # There are some @-mentions mixed in with personals template_payload['mention'] = True template_payload['reply_warning'] = True headers['Reply-To'] = "Nobody <%s>" % ( settings.NOREPLY_EMAIL_ADDRESS, ) # Give users a one-click unsubscribe link they can use to stop getting # missed message emails without having to log in first. unsubscribe_link = one_click_unsubscribe_link(user_profile, "missed_messages") template_payload["unsubscribe_link"] = unsubscribe_link subject = "Missed Zulip%s from %s" % (plural_messages, sender_str) from_email = "%s (via Zulip) <%s>" % (sender_str, settings.NOREPLY_EMAIL_ADDRESS) text_content = loader.render_to_string('zerver/missed_message_email.txt', template_payload) html_content = loader.render_to_string( 'zerver/missed_message_email_html.txt', template_payload) msg = EmailMultiAlternatives(subject, text_content, from_email, [user_profile.email], headers=headers) msg.attach_alternative(html_content, "text/html") msg.send() user_profile.last_reminder = datetime.datetime.now() user_profile.save(update_fields=['last_reminder'])
def do_send_missedmessage_events_reply_in_zulip(user_profile, missed_messages, message_count): # type: (UserProfile, List[Message], int) -> None """ Send a reminder email to a user if she's missed some PMs by being offline. The email will have its reply to address set to a limited used email address that will send a zulip message to the correct recipient. This allows the user to respond to missed PMs, huddles, and @-mentions directly from the email. `user_profile` is the user to send the reminder to `missed_messages` is a list of Message objects to remind about they should all have the same recipient and subject """ from zerver.context_processors import common_context # Disabled missedmessage emails internally if not user_profile.enable_offline_email_notifications: return recipients = set( (msg.recipient_id, msg.subject) for msg in missed_messages) if len(recipients) != 1: raise ValueError( 'All missed_messages must have the same recipient and subject %r' % recipients) unsubscribe_link = one_click_unsubscribe_link(user_profile, "missed_messages") context = common_context(user_profile) context.update({ 'name': user_profile.full_name, 'messages': build_message_list(user_profile, missed_messages), 'message_count': message_count, 'mention': missed_messages[0].recipient.type == Recipient.STREAM, 'unsubscribe_link': unsubscribe_link, }) # If this setting (email mirroring integration) is enabled, only then # can users reply to email to send message to Zulip. Thus, one must # ensure to display warning in the template. if settings.EMAIL_GATEWAY_PATTERN: context.update({ 'reply_warning': False, 'reply_to_zulip': True, }) else: context.update({ 'reply_warning': True, 'reply_to_zulip': False, }) from zerver.lib.email_mirror import create_missed_message_address reply_to_address = create_missed_message_address(user_profile, missed_messages[0]) if reply_to_address == FromAddress.NOREPLY: reply_to_name = None else: reply_to_name = "Zulip" senders = list(set(m.sender for m in missed_messages)) if (missed_messages[0].recipient.type == Recipient.HUDDLE): display_recipient = get_display_recipient(missed_messages[0].recipient) # Make sure that this is a list of strings, not a string. assert not isinstance(display_recipient, Text) other_recipients = [ r['full_name'] for r in display_recipient if r['id'] != user_profile.id ] context.update({'group_pm': True}) if len(other_recipients) == 2: huddle_display_name = u"%s" % (" and ".join(other_recipients)) context.update({'huddle_display_name': huddle_display_name}) elif len(other_recipients) == 3: huddle_display_name = u"%s, %s, and %s" % ( other_recipients[0], other_recipients[1], other_recipients[2]) context.update({'huddle_display_name': huddle_display_name}) else: huddle_display_name = u"%s, and %s others" % (', '.join( other_recipients[:2]), len(other_recipients) - 2) context.update({'huddle_display_name': huddle_display_name}) elif (missed_messages[0].recipient.type == Recipient.PERSONAL): context.update({'private_message': True}) else: # Keep only the senders who actually mentioned the user # # TODO: When we add wildcard mentions that send emails, add # them to the filter here. senders = list( set(m.sender for m in missed_messages if UserMessage.objects.filter( message=m, user_profile=user_profile, flags=UserMessage.flags.mentioned).exists())) context.update({'at_mention': True}) context.update({ 'sender_str': ", ".join(sender.full_name for sender in senders), 'realm_str': user_profile.realm.name, }) from_name, from_address = None, None if len(senders) == 1 and settings.SEND_MISSED_MESSAGE_EMAILS_AS_USER: # If this setting is enabled, you can reply to the Zulip # missed message emails directly back to the original sender. # However, one must ensure the Zulip server is in the SPF # record for the domain, or there will be spam/deliverability # problems. sender = senders[0] from_name, from_address = (sender.full_name, sender.email) context.update({ 'reply_warning': False, 'reply_to_zulip': False, }) email_dict = { 'template_prefix': 'zerver/emails/missed_message', 'to_user_id': user_profile.id, 'from_name': from_name, 'from_address': from_address, 'reply_to_email': formataddr((reply_to_name, reply_to_address)), 'context': context } queue_json_publish("missedmessage_email_senders", email_dict, send_email_from_dict) user_profile.last_reminder = timezone_now() user_profile.save(update_fields=['last_reminder'])
def handle_push_notification(user_profile_id, missed_message): # type: (int, Dict[str, Any]) -> None try: user_profile = get_user_profile_by_id(user_profile_id) if not (receives_offline_notifications(user_profile) or receives_online_notifications(user_profile)): return umessage = UserMessage.objects.get( user_profile=user_profile, message__id=missed_message['message_id']) message = umessage.message if umessage.flags.read: return sender_str = message.sender.full_name android_devices = [ device for device in PushDeviceToken.objects.filter( user=user_profile, kind=PushDeviceToken.GCM) ] apple_devices = list( PushDeviceToken.objects.filter(user=user_profile, kind=PushDeviceToken.APNS)) if apple_devices or android_devices: # TODO: set badge count in a better way # Determine what alert string to display based on the missed messages if message.recipient.type == Recipient.HUDDLE: alert = "New private group message from %s" % (sender_str, ) elif message.recipient.type == Recipient.PERSONAL: alert = "New private message from %s" % (sender_str, ) elif message.recipient.type == Recipient.STREAM: alert = "New mention from %s" % (sender_str, ) else: alert = "New Zulip mentions and private messages from %s" % ( sender_str, ) if apple_devices: apple_extra_data = { 'alert': alert, 'message_ids': [message.id], } send_apple_push_notification(user_profile.id, apple_devices, badge=1, zulip=apple_extra_data) if android_devices: content = message.content content_truncated = (len(content) > 200) if content_truncated: content = content[:200] + "..." android_data = { 'user': user_profile.email, 'event': 'message', 'alert': alert, 'zulip_message_id': message.id, # message_id is reserved for CCS 'time': datetime_to_timestamp(message.pub_date), 'content': content, 'content_truncated': content_truncated, 'sender_email': message.sender.email, 'sender_full_name': message.sender.full_name, 'sender_avatar_url': avatar_url(message.sender), } if message.recipient.type == Recipient.STREAM: android_data['recipient_type'] = "stream" android_data['stream'] = get_display_recipient( message.recipient) android_data['topic'] = message.subject elif message.recipient.type in (Recipient.HUDDLE, Recipient.PERSONAL): android_data['recipient_type'] = "private" send_android_push_notification(android_devices, android_data) except UserMessage.DoesNotExist: logging.error("Could not find UserMessage with message_id %s" % (missed_message['message_id'], ))
def do_send_missedmessage_events_reply_in_zulip(user_profile: UserProfile, missed_messages: List[Dict[ str, Any]], message_count: int) -> None: """ Send a reminder email to a user if she's missed some PMs by being offline. The email will have its reply to address set to a limited used email address that will send a zulip message to the correct recipient. This allows the user to respond to missed PMs, huddles, and @-mentions directly from the email. `user_profile` is the user to send the reminder to `missed_messages` is a list of dictionaries to Message objects and other data for a group of messages that share a recipient (and topic) """ from zerver.context_processors import common_context # Disabled missedmessage emails internally if not user_profile.enable_offline_email_notifications: return recipients = set((msg['message'].recipient_id, msg['message'].topic_name()) for msg in missed_messages) if len(recipients) != 1: raise ValueError( 'All missed_messages must have the same recipient and topic %r' % (recipients, )) # This link is no longer a part of the email, but keeping the code in case # we find a clean way to add it back in the future unsubscribe_link = one_click_unsubscribe_link(user_profile, "missed_messages") context = common_context(user_profile) context.update({ 'name': user_profile.full_name, 'message_count': message_count, 'unsubscribe_link': unsubscribe_link, 'realm_name_in_notifications': user_profile.realm_name_in_notifications, 'show_message_content': message_content_allowed_in_missedmessage_emails(user_profile) }) triggers = list(message['trigger'] for message in missed_messages) unique_triggers = set(triggers) context.update({ 'mention': 'mentioned' in unique_triggers or 'wildcard_mentioned' in unique_triggers, 'stream_email_notify': 'stream_email_notify' in unique_triggers, 'mention_count': triggers.count('mentioned') + triggers.count("wildcard_mentioned"), }) # If this setting (email mirroring integration) is enabled, only then # can users reply to email to send message to Zulip. Thus, one must # ensure to display warning in the template. if settings.EMAIL_GATEWAY_PATTERN: context.update({ 'reply_to_zulip': True, }) else: context.update({ 'reply_to_zulip': False, }) from zerver.lib.email_mirror import create_missed_message_address reply_to_address = create_missed_message_address( user_profile, missed_messages[0]['message']) if reply_to_address == FromAddress.NOREPLY: reply_to_name = None else: reply_to_name = "Zulip" narrow_url = get_narrow_url(user_profile, missed_messages[0]['message']) context.update({ 'narrow_url': narrow_url, }) senders = list(set(m['message'].sender for m in missed_messages)) if (missed_messages[0]['message'].recipient.type == Recipient.HUDDLE): display_recipient = get_display_recipient( missed_messages[0]['message'].recipient) # Make sure that this is a list of strings, not a string. assert not isinstance(display_recipient, str) other_recipients = [ r['full_name'] for r in display_recipient if r['id'] != user_profile.id ] context.update({'group_pm': True}) if len(other_recipients) == 2: huddle_display_name = " and ".join(other_recipients) context.update({'huddle_display_name': huddle_display_name}) elif len(other_recipients) == 3: huddle_display_name = "%s, %s, and %s" % ( other_recipients[0], other_recipients[1], other_recipients[2]) context.update({'huddle_display_name': huddle_display_name}) else: huddle_display_name = "%s, and %s others" % (', '.join( other_recipients[:2]), len(other_recipients) - 2) context.update({'huddle_display_name': huddle_display_name}) elif (missed_messages[0]['message'].recipient.type == Recipient.PERSONAL): context.update({'private_message': True}) elif (context['mention'] or context['stream_email_notify']): # Keep only the senders who actually mentioned the user if context['mention']: senders = list( set(m['message'].sender for m in missed_messages if m['trigger'] == 'mentioned' or m['trigger'] == 'wildcard_mentioned')) message = missed_messages[0]['message'] stream = Stream.objects.only('id', 'name').get(id=message.recipient.type_id) stream_header = "%s > %s" % (stream.name, message.topic_name()) context.update({ 'stream_header': stream_header, }) else: raise AssertionError("Invalid messages!") # If message content is disabled, then flush all information we pass to email. if not message_content_allowed_in_missedmessage_emails(user_profile): context.update({ 'reply_to_zulip': False, 'messages': [], 'sender_str': "", 'realm_str': user_profile.realm.name, 'huddle_display_name': "", }) else: context.update({ 'messages': build_message_list(user_profile, list(m['message'] for m in missed_messages)), 'sender_str': ", ".join(sender.full_name for sender in senders), 'realm_str': user_profile.realm.name, }) from_name = "Zulip missed messages" # type: str from_address = FromAddress.NOREPLY if len(senders) == 1 and settings.SEND_MISSED_MESSAGE_EMAILS_AS_USER: # If this setting is enabled, you can reply to the Zulip # missed message emails directly back to the original sender. # However, one must ensure the Zulip server is in the SPF # record for the domain, or there will be spam/deliverability # problems. sender = senders[0] from_name, from_address = (sender.full_name, sender.email) context.update({ 'reply_to_zulip': False, }) email_dict = { 'template_prefix': 'zerver/emails/missed_message', 'to_user_ids': [user_profile.id], 'from_name': from_name, 'from_address': from_address, 'reply_to_email': formataddr((reply_to_name, reply_to_address)), 'context': context } queue_json_publish("email_senders", email_dict) user_profile.last_reminder = timezone_now() user_profile.save(update_fields=['last_reminder'])
def do_send_missedmessage_events_reply_in_zulip(user_profile: UserProfile, missed_messages: List[Dict[ str, Any]], message_count: int) -> None: """ Send a reminder email to a user if she's missed some PMs by being offline. The email will have its reply to address set to a limited used email address that will send a Zulip message to the correct recipient. This allows the user to respond to missed PMs, huddles, and @-mentions directly from the email. `user_profile` is the user to send the reminder to `missed_messages` is a list of dictionaries to Message objects and other data for a group of messages that share a recipient (and topic) """ from zerver.context_processors import common_context recipients = {(msg["message"].recipient_id, msg["message"].topic_name()) for msg in missed_messages} if len(recipients) != 1: raise ValueError( f"All missed_messages must have the same recipient and topic {recipients!r}", ) # This link is no longer a part of the email, but keeping the code in case # we find a clean way to add it back in the future unsubscribe_link = one_click_unsubscribe_link(user_profile, "missed_messages") context = common_context(user_profile) context.update( name=user_profile.full_name, message_count=message_count, unsubscribe_link=unsubscribe_link, realm_name_in_notifications=user_profile.realm_name_in_notifications, ) mentioned_user_group_name = get_mentioned_user_group_name( missed_messages, user_profile) triggers = [message["trigger"] for message in missed_messages] unique_triggers = set(triggers) context.update( mention="mentioned" in unique_triggers or "wildcard_mentioned" in unique_triggers, stream_email_notify="stream_email_notify" in unique_triggers, mention_count=triggers.count("mentioned") + triggers.count("wildcard_mentioned"), mentioned_user_group_name=mentioned_user_group_name, ) # If this setting (email mirroring integration) is enabled, only then # can users reply to email to send message to Zulip. Thus, one must # ensure to display warning in the template. if settings.EMAIL_GATEWAY_PATTERN: context.update(reply_to_zulip=True, ) else: context.update(reply_to_zulip=False, ) from zerver.lib.email_mirror import create_missed_message_address reply_to_address = create_missed_message_address( user_profile, missed_messages[0]["message"]) if reply_to_address == FromAddress.NOREPLY: reply_to_name = "" else: reply_to_name = "Zulip" narrow_url = get_narrow_url(user_profile, missed_messages[0]["message"]) context.update(narrow_url=narrow_url, ) senders = list({m["message"].sender for m in missed_messages}) if missed_messages[0]["message"].recipient.type == Recipient.HUDDLE: display_recipient = get_display_recipient( missed_messages[0]["message"].recipient) # Make sure that this is a list of strings, not a string. assert not isinstance(display_recipient, str) other_recipients = [ r["full_name"] for r in display_recipient if r["id"] != user_profile.id ] context.update(group_pm=True) if len(other_recipients) == 2: huddle_display_name = " and ".join(other_recipients) context.update(huddle_display_name=huddle_display_name) elif len(other_recipients) == 3: huddle_display_name = ( f"{other_recipients[0]}, {other_recipients[1]}, and {other_recipients[2]}" ) context.update(huddle_display_name=huddle_display_name) else: huddle_display_name = "{}, and {} others".format( ", ".join(other_recipients[:2]), len(other_recipients) - 2) context.update(huddle_display_name=huddle_display_name) elif missed_messages[0]["message"].recipient.type == Recipient.PERSONAL: context.update(private_message=True) elif context["mention"] or context["stream_email_notify"]: # Keep only the senders who actually mentioned the user if context["mention"]: senders = list({ m["message"].sender for m in missed_messages if m["trigger"] == "mentioned" or m["trigger"] == "wildcard_mentioned" }) message = missed_messages[0]["message"] stream = Stream.objects.only("id", "name").get(id=message.recipient.type_id) stream_header = f"{stream.name} > {message.topic_name()}" context.update(stream_header=stream_header, ) else: raise AssertionError("Invalid messages!") # If message content is disabled, then flush all information we pass to email. if not message_content_allowed_in_missedmessage_emails(user_profile): realm = user_profile.realm context.update( reply_to_zulip=False, messages=[], sender_str="", realm_str=realm.name, huddle_display_name="", show_message_content=False, message_content_disabled_by_user=not user_profile. message_content_in_email_notifications, message_content_disabled_by_realm=not realm. message_content_allowed_in_email_notifications, ) else: context.update( messages=build_message_list( user=user_profile, messages=[m["message"] for m in missed_messages], stream_map={}, ), sender_str=", ".join(sender.full_name for sender in senders), realm_str=user_profile.realm.name, show_message_content=True, ) with override_language(user_profile.default_language): from_name: str = _("Zulip notifications") from_address = FromAddress.NOREPLY if len(senders) == 1 and settings.SEND_MISSED_MESSAGE_EMAILS_AS_USER: # If this setting is enabled, you can reply to the Zulip # message notification emails directly back to the original sender. # However, one must ensure the Zulip server is in the SPF # record for the domain, or there will be spam/deliverability # problems. # # Also, this setting is not really compatible with # EMAIL_ADDRESS_VISIBILITY_ADMINS. sender = senders[0] from_name, from_address = (sender.full_name, sender.email) context.update(reply_to_zulip=False, ) email_dict = { "template_prefix": "zerver/emails/missed_message", "to_user_ids": [user_profile.id], "from_name": from_name, "from_address": from_address, "reply_to_email": str(Address(display_name=reply_to_name, addr_spec=reply_to_address)), "context": context, } queue_json_publish("email_senders", email_dict) user_profile.last_reminder = timezone_now() user_profile.save(update_fields=["last_reminder"])
def handle_push_notification(user_profile_id, missed_message): # type: (int, Dict[str, Any]) -> None try: user_profile = get_user_profile_by_id(user_profile_id) if not (receives_offline_notifications(user_profile) or receives_online_notifications(user_profile)): return umessage = UserMessage.objects.get(user_profile=user_profile, message__id=missed_message['message_id']) message = umessage.message if umessage.flags.read: return sender_str = message.sender.full_name android_devices = [device for device in PushDeviceToken.objects.filter(user=user_profile, kind=PushDeviceToken.GCM)] apple_devices = list(PushDeviceToken.objects.filter(user=user_profile, kind=PushDeviceToken.APNS)) if apple_devices or android_devices: # TODO: set badge count in a better way # Determine what alert string to display based on the missed messages if message.recipient.type == Recipient.HUDDLE: alert = "New private group message from %s" % (sender_str,) elif message.recipient.type == Recipient.PERSONAL: alert = "New private message from %s" % (sender_str,) elif message.recipient.type == Recipient.STREAM: alert = "New mention from %s" % (sender_str,) else: alert = "New Zulip mentions and private messages from %s" % (sender_str,) if apple_devices: apple_extra_data = { 'alert': alert, 'message_ids': [message.id], } send_apple_push_notification(user_profile.id, apple_devices, badge=1, zulip=apple_extra_data) if android_devices: content = message.content content_truncated = (len(content) > 200) if content_truncated: content = content[:200] + "..." android_data = { 'user': user_profile.email, 'event': 'message', 'alert': alert, 'zulip_message_id': message.id, # message_id is reserved for CCS 'time': datetime_to_timestamp(message.pub_date), 'content': content, 'content_truncated': content_truncated, 'sender_email': message.sender.email, 'sender_full_name': message.sender.full_name, 'sender_avatar_url': avatar_url(message.sender), } if message.recipient.type == Recipient.STREAM: android_data['recipient_type'] = "stream" android_data['stream'] = get_display_recipient(message.recipient) android_data['topic'] = message.subject elif message.recipient.type in (Recipient.HUDDLE, Recipient.PERSONAL): android_data['recipient_type'] = "private" send_android_push_notification(android_devices, android_data) except UserMessage.DoesNotExist: logging.error("Could not find UserMessage with message_id %s" % (missed_message['message_id'],))