mirror of
https://github.com/zulip/zulip.git
synced 2025-11-07 15:33:30 +00:00
Annotate zerver/lib/notifications.py.
This commit is contained in:
@@ -1,15 +1,25 @@
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
from typing import Any, Tuple
|
from typing import Any, Tuple, Iterable, Optional
|
||||||
|
|
||||||
|
import mandrill
|
||||||
from confirmation.models import Confirmation
|
from confirmation.models import Confirmation
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.mail import EmailMultiAlternatives
|
from django.core.mail import EmailMultiAlternatives
|
||||||
from django.template import loader
|
from django.template import loader
|
||||||
from zerver.decorator import statsd_increment, uses_mandrill
|
from zerver.decorator import statsd_increment, uses_mandrill
|
||||||
from zerver.models import Recipient, ScheduledJob, UserMessage, \
|
from zerver.models import (
|
||||||
Stream, get_display_recipient, get_user_profile_by_email, \
|
Recipient,
|
||||||
get_user_profile_by_id, receives_offline_notifications, \
|
ScheduledJob,
|
||||||
get_context_for_message, Message
|
UserMessage,
|
||||||
|
Stream,
|
||||||
|
get_display_recipient,
|
||||||
|
UserProfile,
|
||||||
|
get_user_profile_by_email,
|
||||||
|
get_user_profile_by_id,
|
||||||
|
receives_offline_notifications,
|
||||||
|
get_context_for_message,
|
||||||
|
Message
|
||||||
|
)
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import re
|
import re
|
||||||
@@ -19,11 +29,13 @@ from six.moves import urllib
|
|||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
def unsubscribe_token(user_profile):
|
def unsubscribe_token(user_profile):
|
||||||
|
# type: (UserProfile) -> str
|
||||||
# Leverage the Django confirmations framework to generate and track unique
|
# Leverage the Django confirmations framework to generate and track unique
|
||||||
# unsubscription tokens.
|
# unsubscription tokens.
|
||||||
return Confirmation.objects.get_link_for_object(user_profile).split("/")[-1]
|
return Confirmation.objects.get_link_for_object(user_profile).split("/")[-1]
|
||||||
|
|
||||||
def one_click_unsubscribe_link(user_profile, endpoint):
|
def one_click_unsubscribe_link(user_profile, endpoint):
|
||||||
|
# type: (UserProfile, str) -> str
|
||||||
"""
|
"""
|
||||||
Generate a unique link that a logged-out user can visit to unsubscribe from
|
Generate a unique link that a logged-out user can visit to unsubscribe from
|
||||||
Zulip e-mails without having to first log in.
|
Zulip e-mails without having to first log in.
|
||||||
@@ -34,6 +46,7 @@ def one_click_unsubscribe_link(user_profile, endpoint):
|
|||||||
return "%s/%s" % (base_url.rstrip("/"), resource_path)
|
return "%s/%s" % (base_url.rstrip("/"), resource_path)
|
||||||
|
|
||||||
def hashchange_encode(string):
|
def hashchange_encode(string):
|
||||||
|
# type: (str) -> str
|
||||||
# Do the same encoding operation as hashchange.encodeHashComponent on the
|
# Do the same encoding operation as hashchange.encodeHashComponent on the
|
||||||
# frontend.
|
# frontend.
|
||||||
# `safe` has a default value of "/", but we want those encoded, too.
|
# `safe` has a default value of "/", but we want those encoded, too.
|
||||||
@@ -41,20 +54,24 @@ def hashchange_encode(string):
|
|||||||
string.encode("utf-8"), safe="").replace(".", "%2E").replace("%", ".")
|
string.encode("utf-8"), safe="").replace(".", "%2E").replace("%", ".")
|
||||||
|
|
||||||
def pm_narrow_url(participants):
|
def pm_narrow_url(participants):
|
||||||
|
# type: (List[str]) -> str
|
||||||
participants.sort()
|
participants.sort()
|
||||||
base_url = "https://%s/#narrow/pm-with/" % (settings.EXTERNAL_HOST,)
|
base_url = "https://%s/#narrow/pm-with/" % (settings.EXTERNAL_HOST,)
|
||||||
return base_url + hashchange_encode(",".join(participants))
|
return base_url + hashchange_encode(",".join(participants))
|
||||||
|
|
||||||
def stream_narrow_url(stream):
|
def stream_narrow_url(stream):
|
||||||
|
# type: (str) -> str
|
||||||
base_url = "https://%s/#narrow/stream/" % (settings.EXTERNAL_HOST,)
|
base_url = "https://%s/#narrow/stream/" % (settings.EXTERNAL_HOST,)
|
||||||
return base_url + hashchange_encode(stream)
|
return base_url + hashchange_encode(stream)
|
||||||
|
|
||||||
def topic_narrow_url(stream, topic):
|
def topic_narrow_url(stream, topic):
|
||||||
|
# type: (str, str) -> str
|
||||||
base_url = "https://%s/#narrow/stream/" % (settings.EXTERNAL_HOST,)
|
base_url = "https://%s/#narrow/stream/" % (settings.EXTERNAL_HOST,)
|
||||||
return "%s%s/topic/%s" % (base_url, hashchange_encode(stream),
|
return "%s%s/topic/%s" % (base_url, hashchange_encode(stream),
|
||||||
hashchange_encode(topic))
|
hashchange_encode(topic))
|
||||||
|
|
||||||
def build_message_list(user_profile, messages):
|
def build_message_list(user_profile, messages):
|
||||||
|
# type: (UserProfile, List[Message]) -> List[Dict[str, Any]]
|
||||||
"""
|
"""
|
||||||
Builds the message list object for the missed message email template.
|
Builds the message list object for the missed message email template.
|
||||||
The messages are collapsed into per-recipient and per-sender blocks, like
|
The messages are collapsed into per-recipient and per-sender blocks, like
|
||||||
@@ -63,12 +80,14 @@ def build_message_list(user_profile, messages):
|
|||||||
messages_to_render = [] # type: List[Dict[str, Any]]
|
messages_to_render = [] # type: List[Dict[str, Any]]
|
||||||
|
|
||||||
def sender_string(message):
|
def sender_string(message):
|
||||||
|
# type: (Message) -> str
|
||||||
sender = ''
|
sender = ''
|
||||||
if message.recipient.type in (Recipient.STREAM, Recipient.HUDDLE):
|
if message.recipient.type in (Recipient.STREAM, Recipient.HUDDLE):
|
||||||
sender = message.sender.full_name
|
sender = message.sender.full_name
|
||||||
return sender
|
return sender
|
||||||
|
|
||||||
def relative_to_full_url(content):
|
def relative_to_full_url(content):
|
||||||
|
# type: (str) -> str
|
||||||
# URLs for uploaded content are of the form
|
# URLs for uploaded content are of the form
|
||||||
# "/user_uploads/abc.png". Make them full paths.
|
# "/user_uploads/abc.png". Make them full paths.
|
||||||
#
|
#
|
||||||
@@ -94,15 +113,18 @@ def build_message_list(user_profile, messages):
|
|||||||
return content
|
return content
|
||||||
|
|
||||||
def fix_plaintext_image_urls(content):
|
def fix_plaintext_image_urls(content):
|
||||||
|
# type: (str) -> str
|
||||||
# Replace image URLs in plaintext content of the form
|
# Replace image URLs in plaintext content of the form
|
||||||
# [image name](image url)
|
# [image name](image url)
|
||||||
# with a simple hyperlink.
|
# with a simple hyperlink.
|
||||||
return re.sub(r"\[(\S*)\]\((\S*)\)", r"\2", content)
|
return re.sub(r"\[(\S*)\]\((\S*)\)", r"\2", content)
|
||||||
|
|
||||||
def fix_emoji_sizes(html):
|
def fix_emoji_sizes(html):
|
||||||
|
# type: (str) -> str
|
||||||
return html.replace(' class="emoji"', ' height="20px"')
|
return html.replace(' class="emoji"', ' height="20px"')
|
||||||
|
|
||||||
def build_message_payload(message):
|
def build_message_payload(message):
|
||||||
|
# type: (Message) -> Dict[str, str]
|
||||||
plain = message.content
|
plain = message.content
|
||||||
plain = fix_plaintext_image_urls(plain)
|
plain = fix_plaintext_image_urls(plain)
|
||||||
plain = relative_to_full_url(plain)
|
plain = relative_to_full_url(plain)
|
||||||
@@ -114,11 +136,13 @@ def build_message_list(user_profile, messages):
|
|||||||
return {'plain': plain, 'html': html}
|
return {'plain': plain, 'html': html}
|
||||||
|
|
||||||
def build_sender_payload(message):
|
def build_sender_payload(message):
|
||||||
|
# type: (Message) -> Dict[str, Any]
|
||||||
sender = sender_string(message)
|
sender = sender_string(message)
|
||||||
return {'sender': sender,
|
return {'sender': sender,
|
||||||
'content': [build_message_payload(message)]}
|
'content': [build_message_payload(message)]}
|
||||||
|
|
||||||
def message_header(user_profile, message):
|
def message_header(user_profile, message):
|
||||||
|
# type: (UserProfile, Message) -> Dict[str, str]
|
||||||
disp_recipient = get_display_recipient(message.recipient)
|
disp_recipient = get_display_recipient(message.recipient)
|
||||||
if message.recipient.type == Recipient.PERSONAL:
|
if message.recipient.type == Recipient.PERSONAL:
|
||||||
header = "You and %s" % (message.sender.full_name)
|
header = "You and %s" % (message.sender.full_name)
|
||||||
@@ -193,6 +217,7 @@ def build_message_list(user_profile, messages):
|
|||||||
|
|
||||||
@statsd_increment("missed_message_reminders")
|
@statsd_increment("missed_message_reminders")
|
||||||
def do_send_missedmessage_events_reply_in_zulip(user_profile, missed_messages, message_count):
|
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.
|
Send a reminder email to a user if she's missed some PMs by being offline.
|
||||||
|
|
||||||
@@ -252,6 +277,7 @@ def do_send_missedmessage_events_reply_in_zulip(user_profile, missed_messages, m
|
|||||||
|
|
||||||
@statsd_increment("missed_message_reminders")
|
@statsd_increment("missed_message_reminders")
|
||||||
def do_send_missedmessage_events(user_profile, missed_messages, message_count):
|
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
|
Send a reminder email and/or push notifications to a user if she's missed some PMs by being offline
|
||||||
|
|
||||||
@@ -314,6 +340,7 @@ def do_send_missedmessage_events(user_profile, missed_messages, message_count):
|
|||||||
|
|
||||||
|
|
||||||
def handle_missedmessage_emails(user_profile_id, missed_email_events):
|
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]
|
message_ids = [event.get('message_id') for event in missed_email_events]
|
||||||
|
|
||||||
user_profile = get_user_profile_by_id(user_profile_id)
|
user_profile = get_user_profile_by_id(user_profile_id)
|
||||||
@@ -364,6 +391,7 @@ def handle_missedmessage_emails(user_profile_id, missed_email_events):
|
|||||||
|
|
||||||
@uses_mandrill
|
@uses_mandrill
|
||||||
def clear_followup_emails_queue(email, mail_client=None):
|
def clear_followup_emails_queue(email, mail_client=None):
|
||||||
|
# type: (str, Optional[mandrill.Mandrill]) -> None
|
||||||
"""
|
"""
|
||||||
Clear out queued emails (from Mandrill's queue) that would otherwise
|
Clear out queued emails (from Mandrill's queue) that would otherwise
|
||||||
be sent to a specific email address. Optionally specify which sender
|
be sent to a specific email address. Optionally specify which sender
|
||||||
@@ -388,6 +416,7 @@ def clear_followup_emails_queue(email, mail_client=None):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def log_digest_event(msg):
|
def log_digest_event(msg):
|
||||||
|
# type: (str) -> None
|
||||||
import logging
|
import logging
|
||||||
logging.basicConfig(filename=settings.DIGEST_LOG_PATH, level=logging.INFO)
|
logging.basicConfig(filename=settings.DIGEST_LOG_PATH, level=logging.INFO)
|
||||||
logging.info(msg)
|
logging.info(msg)
|
||||||
@@ -396,6 +425,7 @@ def log_digest_event(msg):
|
|||||||
def send_future_email(recipients, email_html, email_text, subject,
|
def send_future_email(recipients, email_html, email_text, subject,
|
||||||
delay=datetime.timedelta(0), sender=None,
|
delay=datetime.timedelta(0), sender=None,
|
||||||
tags=[], mail_client=None):
|
tags=[], mail_client=None):
|
||||||
|
# type: (List[Dict[str, Any]], str, str, str, datetime.timedelta, Optional[Dict[str, str]], Iterable[str], Optional[mandrill.Mandrill]) -> None
|
||||||
"""
|
"""
|
||||||
Sends email via Mandrill, with optional delay
|
Sends email via Mandrill, with optional delay
|
||||||
|
|
||||||
@@ -483,6 +513,7 @@ def send_future_email(recipients, email_html, email_text, subject,
|
|||||||
def send_local_email_template_with_delay(recipients, template_prefix,
|
def send_local_email_template_with_delay(recipients, template_prefix,
|
||||||
template_payload, delay,
|
template_payload, delay,
|
||||||
tags=[], sender={'email': settings.NOREPLY_EMAIL_ADDRESS, 'name': 'Zulip'}):
|
tags=[], sender={'email': settings.NOREPLY_EMAIL_ADDRESS, 'name': 'Zulip'}):
|
||||||
|
# type: (List[Dict[str, Any]], str, Dict[str, str], datetime.timedelta, Iterable[str], Dict[str, str]) -> None
|
||||||
html_content = loader.render_to_string(template_prefix + ".html", template_payload)
|
html_content = loader.render_to_string(template_prefix + ".html", template_payload)
|
||||||
text_content = loader.render_to_string(template_prefix + ".text", template_payload)
|
text_content = loader.render_to_string(template_prefix + ".text", template_payload)
|
||||||
subject = loader.render_to_string(template_prefix + ".subject", template_payload).strip()
|
subject = loader.render_to_string(template_prefix + ".subject", template_payload).strip()
|
||||||
@@ -496,6 +527,7 @@ def send_local_email_template_with_delay(recipients, template_prefix,
|
|||||||
tags=tags)
|
tags=tags)
|
||||||
|
|
||||||
def enqueue_welcome_emails(email, name):
|
def enqueue_welcome_emails(email, name):
|
||||||
|
# type: (str, str) -> None
|
||||||
sender = {'email': 'wdaher@zulip.com', 'name': 'Waseem Daher'}
|
sender = {'email': 'wdaher@zulip.com', 'name': 'Waseem Daher'}
|
||||||
if settings.VOYAGER:
|
if settings.VOYAGER:
|
||||||
sender = {'email': settings.ZULIP_ADMINISTRATOR, 'name': 'Zulip'}
|
sender = {'email': settings.ZULIP_ADMINISTRATOR, 'name': 'Zulip'}
|
||||||
@@ -528,6 +560,7 @@ def enqueue_welcome_emails(email, name):
|
|||||||
sender=sender)
|
sender=sender)
|
||||||
|
|
||||||
def convert_html_to_markdown(html):
|
def convert_html_to_markdown(html):
|
||||||
|
# type: (str) -> unicode
|
||||||
# On Linux, the tool installs as html2markdown, and there's a command called
|
# On Linux, the tool installs as html2markdown, and there's a command called
|
||||||
# html2text that does something totally different. On OSX, the tool installs
|
# html2text that does something totally different. On OSX, the tool installs
|
||||||
# as html2text.
|
# as html2text.
|
||||||
|
|||||||
Reference in New Issue
Block a user