More variable calculation for email notifs

Include name of the person we're sending to and add summary text at the top giving an overview of what's happened.
pull/759/head
David Baker 2016-04-25 18:27:04 +01:00
parent 290f125a13
commit 7b4715bad7
3 changed files with 76 additions and 19 deletions

View File

@ -1,7 +1,8 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<body> <body>
<h1>{{ summaryText }}</h1> <div className="salutation">Hi {{ user_display_name }},</div>
<div className="summarytext">{{ summary_text }}</div>
<div class="content"> <div class="content">
{% for room in rooms %} {% for room in rooms %}
{% include 'room.html' with context %} {% include 'room.html' with context %}

View File

@ -21,11 +21,19 @@ import email.mime.multipart
from email.mime.text import MIMEText from email.mime.text import MIMEText
from synapse.util.async import concurrently_execute from synapse.util.async import concurrently_execute
from synapse.util.room_name import calculate_room_name from synapse.util.presentable_names import calculate_room_name, name_from_member_event
from synapse.types import UserID
from synapse.api.errors import StoreError
import jinja2 import jinja2
MESSAGE_FROM_PERSON_IN_ROOM = "You have a message from %s in the %s room"
MESSAGE_FROM_PERSON = "You have a message from %s"
MESSAGES_IN_ROOM = "There are some messages for you in the %s room"
MESSAGES_IN_ROOMS = "Here are some messages you may have missed"
class Mailer(object): class Mailer(object):
def __init__(self, hs): def __init__(self, hs):
self.hs = hs self.hs = hs
@ -55,6 +63,13 @@ class Mailer(object):
# notifications # notifications
state_by_room = {} state_by_room = {}
try:
user_display_name = yield self.store.get_profile_displayname(
UserID.from_string(user_id).localpart
)
except StoreError:
user_display_name = user_id
@defer.inlineCallbacks @defer.inlineCallbacks
def _fetch_room_state(room_id): def _fetch_room_state(room_id):
room_state = yield self.state_handler.get_current_state(room_id) room_state = yield self.state_handler.get_current_state(room_id)
@ -70,8 +85,14 @@ class Mailer(object):
) for r in rooms_in_order ) for r in rooms_in_order
] ]
summary_text = yield self.make_summary_text(
notifs_by_room, state_by_room, user_id
)
template_vars = { template_vars = {
"user_display_name": user_display_name,
"unsubscribe_link": self.make_unsubscribe_link(), "unsubscribe_link": self.make_unsubscribe_link(),
"summary_text": summary_text,
"rooms": rooms, "rooms": rooms,
} }
@ -93,6 +114,38 @@ class Mailer(object):
room_vars['title'] = calculate_room_name(room_state, user_id) room_vars['title'] = calculate_room_name(room_state, user_id)
return room_vars return room_vars
@defer.inlineCallbacks
def make_summary_text(self, notifs_by_room, state_by_room, user_id):
if len(notifs_by_room) == 1:
room_id = notifs_by_room.keys()[0]
sender_name = None
if len(notifs_by_room[room_id]) == 1:
# 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
)
event = yield self.store.get_event(
notifs_by_room[room_id][0]["event_id"]
)
if ("m.room.member", event.sender) in state_by_room[room_id]:
state_event = state_by_room[room_id][("m.room.member", event.sender)]
sender_name = name_from_member_event(state_event)
if sender_name is not None and room_name is not None:
defer.returnValue(
MESSAGE_FROM_PERSON_IN_ROOM % (sender_name, room_name)
)
elif sender_name is not None:
defer.returnValue(MESSAGE_FROM_PERSON % (sender_name,))
else:
room_name = calculate_room_name(state_by_room[room_id], user_id)
defer.returnValue(MESSAGES_IN_ROOM % (room_name,))
else:
defer.returnValue(MESSAGES_IN_ROOMS)
defer.returnValue("Some thing have occurred in some rooms")
def make_unsubscribe_link(self): def make_unsubscribe_link(self):
return "https://vector.im/#/settings" # XXX: matrix.to return "https://vector.im/#/settings" # XXX: matrix.to
@ -104,4 +157,4 @@ def deduped_ordered_list(l):
if item not in seen: if item not in seen:
seen.add(item) seen.add(item)
ret.append(item) ret.append(item)
return ret return ret

View File

@ -22,7 +22,7 @@ ALIAS_RE = re.compile(r"^#.*:.+$")
ALL_ALONE = "Empty Room" ALL_ALONE = "Empty Room"
def calculate_room_name(room_state, user_id): def calculate_room_name(room_state, user_id, fallback_to_members=True):
# does it have a name? # does it have a name?
if ("m.room.name", "") in room_state: if ("m.room.name", "") in room_state:
m_room_name = room_state[("m.room.name", "")] m_room_name = room_state[("m.room.name", "")]
@ -34,13 +34,13 @@ def calculate_room_name(room_state, user_id):
canon_alias = room_state[("m.room.canonical_alias", "")] canon_alias = room_state[("m.room.canonical_alias", "")]
if ( if (
canon_alias.content and canon_alias.content["alias"] and canon_alias.content and canon_alias.content["alias"] and
looks_like_an_alias(canon_alias.content["alias"]) _looks_like_an_alias(canon_alias.content["alias"])
): ):
return canon_alias.content["alias"] return canon_alias.content["alias"]
# at this point we're going to need to search the state by all state keys # at this point we're going to need to search the state by all state keys
# for an event type, so rearrange the data structure # for an event type, so rearrange the data structure
room_state_bytype = state_as_two_level_dict(room_state) room_state_bytype = _state_as_two_level_dict(room_state)
# right then, any aliases at all? # right then, any aliases at all?
if "m.room.aliases" in room_state_bytype: if "m.room.aliases" in room_state_bytype:
@ -49,7 +49,7 @@ def calculate_room_name(room_state, user_id):
first_alias_event = m_room_aliases.values()[0] first_alias_event = m_room_aliases.values()[0]
if first_alias_event.content and first_alias_event.content["aliases"]: if first_alias_event.content and first_alias_event.content["aliases"]:
the_aliases = first_alias_event.content["aliases"] the_aliases = first_alias_event.content["aliases"]
if len(the_aliases) > 0 and looks_like_an_alias(the_aliases[0]): if len(the_aliases) > 0 and _looks_like_an_alias(the_aliases[0]):
return the_aliases[0] return the_aliases[0]
my_member_event = None my_member_event = None
@ -66,6 +66,9 @@ def calculate_room_name(room_state, user_id):
else: else:
return "Room Invite" return "Room Invite"
if not fallback_to_members:
return None
# we're going to have to generate a name based on who's in the room, # we're going to have to generate a name based on who's in the room,
# so find out who is in the room that isn't the user. # so find out who is in the room that isn't the user.
if "m.room.member" in room_state_bytype: if "m.room.member" in room_state_bytype:
@ -105,17 +108,6 @@ def calculate_room_name(room_state, user_id):
return descriptor_from_member_events(other_members) return descriptor_from_member_events(other_members)
def state_as_two_level_dict(state):
ret = {}
for k, v in state.items():
ret.setdefault(k[0], {})[k[1]] = v
return ret
def looks_like_an_alias(string):
return ALIAS_RE.match(string) is not None
def descriptor_from_member_events(member_events): def descriptor_from_member_events(member_events):
if len(member_events) == 0: if len(member_events) == 0:
return "nobody" return "nobody"
@ -139,4 +131,15 @@ def name_from_member_event(member_event):
member_event.content["displayname"] member_event.content["displayname"]
): ):
return member_event.content["displayname"] return member_event.content["displayname"]
return member_event.state_key return member_event.state_key
def _state_as_two_level_dict(state):
ret = {}
for k, v in state.items():
ret.setdefault(k[0], {})[k[1]] = v
return ret
def _looks_like_an_alias(string):
return ALIAS_RE.match(string) is not None