Esempio n. 1
0
def get_email_ids(message):
    parent_email_id = None
    other_email_ids = []
    if message.parent_id:
        parent = Message._byID(message.parent_id, data=True)
        if parent.email_id:
            other_email_ids.append(parent.email_id)
            parent_email_id = parent.email_id

    if message.first_message:
        first_message = Message._byID(message.first_message, data=True)
        if first_message.email_id:
            other_email_ids.append(first_message.email_id)

    return parent_email_id, other_email_ids
Esempio n. 2
0
def get_email_ids(message):
    parent_email_id = None
    other_email_ids = []
    if message.parent_id:
        parent = Message._byID(message.parent_id, data=True)
        if parent.email_id:
            other_email_ids.append(parent.email_id)
            parent_email_id = parent.email_id

    if message.first_message:
        first_message = Message._byID(message.first_message, data=True)
        if first_message.email_id:
            other_email_ids.append(first_message.email_id)

    return parent_email_id, other_email_ids
Esempio n. 3
0
def get_reply_to_address(message):
    """Construct a reply-to address that encodes the message id.

    The address is of the form:
        zendeskreply+{message_id36}-{email_mac}

    where the mac is generated from {message_id36} using the
    `modmail_email_secret`

    The reply address should be configured with the inbound email service so
    that replies to our messages are routed back to the app somehow. For mailgun
    this involves adding a Routes filter for messages sent to
    "zendeskreply\+*@". to be forwarded to POST /api/zendeskreply.

    """

    # all email replies are treated as replies to the first message in the
    # conversation. this is to get around some peculiarities of zendesk
    if message.first_message:
        first_message = Message._byID(message.first_message, data=True)
    else:
        first_message = message
    email_id = first_message._id36

    email_mac = hmac.new(
        g.secrets['modmail_email_secret'], email_id, hashlib.sha256).hexdigest()
    reply_id = "zendeskreply+{email_id}-{email_mac}".format(
        email_id=email_id, email_mac=email_mac)

    sr = Subreddit._byID(message.sr_id, data=True)
    return "{brander_community_abbr}/{subreddit} mail <{reply_id}@{domain}>".format(
        subreddit=sr.name, reply_id=reply_id, domain=g.modmail_email_domain, brander_community_abbr = g.brander_community_abbr)
Esempio n. 4
0
    def __init__(self, thing, delete = False, report = True):
        was_comment = getattr(thing, 'was_comment', False)
        permalink = thing.permalink
        # don't allow replying to self unless it's modmail
        valid_recipient = (thing.author_id != c.user._id or
                           thing.sr_id)

        can_reply = (c.user_is_loggedin and
                     getattr(thing, "repliable", True) and
                     valid_recipient)
        can_block = True
        can_mute = False
        is_admin_message = False
        del_on_recipient = (isinstance(thing, Message) and
                            thing.del_on_recipient)

        if not was_comment:
            first_message = thing
            if getattr(thing, 'first_message', False):
                first_message = Message._byID(thing.first_message, data=True)

            if thing.sr_id:
                sr = thing.subreddit_slow
                is_admin_message = '/r/%s' % sr.name == g.admin_message_acct

                if (sr.is_muted(first_message.author_slow) or
                        (first_message.to_id and
                            sr.is_muted(first_message.recipient_slow))):
                    can_reply = False

                can_mute = sr.can_mute(c.user, thing.author_slow)

        if not was_comment and thing.display_author:
            can_block = False

        if was_comment:
            link = thing.link_slow
            if link.archived or link.locked:
                can_reply = False

        # Allow comment-reply messages to have links to the full thread.
        if was_comment:
            self.full_comment_path = thing.link_permalink
            self.full_comment_count = thing.full_comment_count

        PrintableButtons.__init__(self, "messagebuttons", thing,
                                  profilepage = c.profilepage,
                                  permalink = permalink,
                                  was_comment = was_comment,
                                  unread = thing.new,
                                  user_is_recipient = thing.user_is_recipient,
                                  can_reply = can_reply,
                                  parent_id = getattr(thing, "parent_id", None),
                                  show_report = True,
                                  show_delete = False,
                                  can_block = can_block,
                                  can_mute = can_mute,
                                  is_admin_message = is_admin_message,
                                  del_on_recipient=del_on_recipient,
                                 )
Esempio n. 5
0
def compute_message_trees(messages):
    from r2.models import Message
    roots = set()
    threads = {}
    mdict = {}
    messages = sorted(messages, key = lambda m: m._date, reverse = True)

    for m in messages:
        if not m._loaded:
            m._load()
        mdict[m._id] = m
        if m.first_message:
            roots.add(m.first_message)
            threads.setdefault(m.first_message, set()).add(m._id)
        else:
            roots.add(m._id)

    # load any top-level messages which are not in the original list
    missing = [m for m in roots if m not in mdict]
    if missing:
        mdict.update(Message._byID(tup(missing),
                                   return_dict = True, data = True))

    # sort threads in chrono order
    for k in threads:
        threads[k] = list(sorted(threads[k]))

    tree = [(root, threads.get(root, [])) for root in roots]
    tree.sort(key = tree_sort_fn, reverse = True)

    return tree
Esempio n. 6
0
def compute_message_trees(messages):
    from r2.models import Message
    roots = set()
    threads = {}
    mdict = {}
    messages = sorted(messages, key=lambda m: m._date, reverse=True)

    for m in messages:
        if not m._loaded:
            m._load()
        mdict[m._id] = m
        if m.first_message:
            roots.add(m.first_message)
            threads.setdefault(m.first_message, set()).add(m._id)
        else:
            roots.add(m._id)

    # load any top-level messages which are not in the original list
    missing = [m for m in roots if m not in mdict]
    if missing:
        mdict.update(Message._byID(tup(missing), return_dict=True, data=True))

    # sort threads in chrono order
    for k in threads:
        threads[k] = list(sorted(threads[k]))

    tree = [(root, threads.get(root, [])) for root in roots]
    tree.sort(key=tree_sort_fn, reverse=True)

    return tree
Esempio n. 7
0
def get_reply_to_address(message):
    """Construct a reply-to address that encodes the message id.

    The address is of the form:
        zendeskreply+{message_id36}-{email_mac}

    where the mac is generated from {message_id36} using the
    `modmail_email_secret`

    The reply address should be configured with the inbound email service so
    that replies to our messages are routed back to the app somehow. For mailgun
    this involves adding a Routes filter for messages sent to
    "zendeskreply\+*@". to be forwarded to POST /api/zendeskreply.

    """

    # all email replies are treated as replies to the first message in the
    # conversation. this is to get around some peculiarities of zendesk
    if message.first_message:
        first_message = Message._byID(message.first_message, data=True)
    else:
        first_message = message
    email_id = first_message._id36

    email_mac = hmac.new(
        g.secrets['modmail_email_secret'], email_id, hashlib.sha256).hexdigest()
    reply_id = "zendeskreply+{email_id}-{email_mac}".format(
        email_id=email_id, email_mac=email_mac)

    sr = Subreddit._byID(message.sr_id, data=True)
    return "r/{subreddit} mail <{reply_id}@{domain}>".format(
        subreddit=sr.name, reply_id=reply_id, domain=g.modmail_email_domain)
Esempio n. 8
0
def get_message_subject(message):
    sr = Subreddit._byID(message.sr_id, data=True)

    if message.first_message:
        first_message = Message._byID(message.first_message, data=True)
        conversation_subject = first_message.subject
    else:
        conversation_subject = message.subject

    return u"[{brander_community_abbr}/{subreddit} mail]: {subject}".format(
        subreddit=sr.name, subject=_force_unicode(conversation_subject, brander_community_abbr=g.brander_community_abbr))
Esempio n. 9
0
def get_message_subject(message):
    sr = Subreddit._byID(message.sr_id, data=True)

    if message.first_message:
        first_message = Message._byID(message.first_message, data=True)
        conversation_subject = first_message.subject
    else:
        conversation_subject = message.subject

    return u"[r/{subreddit} mail]: {subject}".format(
        subreddit=sr.name, subject=_force_unicode(conversation_subject))
Esempio n. 10
0
    def __init__(self, thing, delete = False, report = True):
        was_comment = getattr(thing, 'was_comment', False)
        permalink = thing.permalink
        # don't allow replying to self unless it's modmail
        valid_recipient = (thing.author_id != c.user._id or
                           thing.sr_id)
        is_muted = False
        can_mute = False
        if not was_comment:
            first_message = thing
            if getattr(thing, 'first_message', False):
                first_message = Message._byID(thing.first_message, data=True)

            if thing.sr_id:
                sr = thing.subreddit_slow
                if feature.is_enabled('modmail_muting', subreddit=sr.name):
                    if (sr.is_muted(first_message.author_slow) or
                            (first_message.to_id and
                                sr.is_muted(first_message.recipient_slow))):
                        is_muted = True

                    if (not sr.is_moderator(thing.author_slow) and
                            sr.is_moderator_with_perms(c.user, 'access', 'mail')):
                        can_mute = True

        can_reply = (c.user_is_loggedin and
                     getattr(thing, "repliable", True) and
                     valid_recipient and
                     not is_muted)
        can_block = True

        if not thing.was_comment and thing.display_author:
            can_block = False

        # Allow comment-reply messages to have links to the full thread.
        if was_comment:
            self.full_comment_path = thing.link_permalink
            self.full_comment_count = thing.full_comment_count

        PrintableButtons.__init__(self, "messagebuttons", thing,
                                  profilepage = c.profilepage,
                                  permalink = permalink,
                                  was_comment = was_comment,
                                  unread = thing.new,
                                  user_is_recipient = thing.user_is_recipient,
                                  can_reply = can_reply,
                                  parent_id = getattr(thing, "parent_id", None),
                                  show_report = True,
                                  show_delete = False,
                                  can_block = can_block,
                                  can_mute = can_mute,
                                 )
Esempio n. 11
0
    def __init__(self, thing, delete = False, report = True):
        was_comment = getattr(thing, 'was_comment', False)
        permalink = thing.permalink
        # don't allow replying to self unless it's modmail
        valid_recipient = (thing.author_id != c.user._id or
                           thing.sr_id)
        is_muted = False
        can_mute = False
        if not was_comment:
            first_message = thing
            if getattr(thing, 'first_message', False):
                first_message = Message._byID(thing.first_message, data=True)

            if thing.sr_id:
                sr = thing.subreddit_slow
                if feature.is_enabled('modmail_muting', subreddit=sr.name):
                    if (sr.is_muted(first_message.author_slow) or
                            (first_message.to_id and
                                sr.is_muted(first_message.recipient_slow))):
                        is_muted = True

                    if (not sr.is_moderator(thing.author_slow) and
                            sr.is_moderator_with_perms(c.user, 'access', 'mail')):
                        can_mute = True

        can_reply = (c.user_is_loggedin and
                     getattr(thing, "repliable", True) and
                     valid_recipient and
                     not is_muted)
        can_block = True

        if not thing.was_comment and thing.display_author:
            can_block = False

        # Allow comment-reply messages to have links to the full thread.
        if was_comment:
            self.full_comment_path = thing.link_permalink
            self.full_comment_count = thing.full_comment_count

        PrintableButtons.__init__(self, "messagebuttons", thing,
                                  profilepage = c.profilepage,
                                  permalink = permalink,
                                  was_comment = was_comment,
                                  unread = thing.new,
                                  user_is_recipient = thing.user_is_recipient,
                                  can_reply = can_reply,
                                  parent_id = getattr(thing, "parent_id", None),
                                  show_report = True,
                                  show_delete = False,
                                  can_block = can_block,
                                  can_mute = can_mute,
                                 )
Esempio n. 12
0
def _conversation(trees, parent):
    from r2.models import Message
    if parent._id in trees:
        convo = trees[parent._id]
        if convo:
            m = Message._byID(convo[0], data=True)
        if not convo or m.first_message == m.parent_id:
            return [(parent._id, convo)]

    # if we get to this point, either we didn't find the conversation,
    # or the first child of the result was not the actual first child.
    # To the database!
    m = Message._query(Message.c.first_message == parent._id, data=True)
    return compute_message_trees([parent] + list(m))
Esempio n. 13
0
def _conversation(trees, parent):
    from r2.models import Message
    if parent._id in trees:
        convo = trees[parent._id]
        if convo:
            m = Message._byID(convo[0], data=True)
        if not convo or m.first_message == m.parent_id:
            return [(parent._id, convo)]

    # if we get to this point, either we didn't find the conversation,
    # or the first child of the result was not the actual first child.
    # To the database!
    m = Message._query(Message.c.first_message == parent._id, data=True)
    return compute_message_trees([parent] + list(m))
Esempio n. 14
0
def _conversation(trees, parent):
    from r2.models import Message
    if parent._id in trees:
        convo = trees[parent._id]
        if convo:
            m = Message._byID(convo[0], data=True)
        if not convo or m.first_message == m.parent_id:
            return [(parent._id, convo)]

    # if we get to this point, either we didn't find the conversation,
    # or the first child of the result was not the actual first child.
    # To the database!
    rules = [Message.c.first_message == parent._id]
    if c.user_is_admin:
        rules.append(Message.c._spam == (True, False))
        rules.append(Message.c._deleted == (True, False))
    m = Message._query(*rules, data=True)
    return compute_message_trees([parent] + list(m))
Esempio n. 15
0
def _conversation(trees, parent):
    from r2.models import Message
    if parent._id in trees:
        convo = trees[parent._id]
        if convo:
            m = Message._byID(convo[0], data = True)
        if not convo or m.first_message == m.parent_id:
            return [(parent._id, convo)]

    # if we get to this point, either we didn't find the conversation,
    # or the first child of the result was not the actual first child.
    # To the database!
    rules = [Message.c.first_message == parent._id]
    if c.user_is_admin:
        rules.append(Message.c._spam == (True, False))
        rules.append(Message.c._deleted == (True, False))
    m = Message._query(*rules, data=True)
    return compute_message_trees([parent] + list(m))
Esempio n. 16
0
    def get_items(self):
        tree = self.get_tree()

        prev_item = next_item = None
        if not self.parent:
            if self.num is not None:
                if self.after:
                    if self.reverse:
                        tree = filter(
                            self._tree_filter_reverse,
                            tree)
                        next_item = self.after._id
                        if len(tree) > self.num:
                            first = tree[-(self.num+1)]
                            prev_item = first[1][-1] if first[1] else first[0]
                            tree = tree[-self.num:]
                    else:
                        prev_item = self.after._id
                        tree = filter(
                            self._tree_filter,
                            tree)
                if len(tree) > self.num:
                    tree = tree[:self.num]
                    last = tree[-1]
                    next_item = last[1][-1] if last[1] else last[0]

        # generate the set of ids to look up and look them up
        message_ids = []
        for root, thread in tree:
            message_ids.append(root)
            message_ids.extend(thread)
        if prev_item:
            message_ids.append(prev_item)

        messages = Message._byID(message_ids, data = True, return_dict = False)
        wrapped = {}
        for m in self.wrap_items(messages):
            if not self._viewable_message(m):
                g.log.warning("%r is not viewable by %s; path is %s" %
                                 (m, c.user.name, request.fullpath))
                continue
            wrapped[m._id] = m

        if prev_item:
            prev_item = wrapped[prev_item]
        if next_item:
            next_item = wrapped[next_item]

        final = []
        for parent, children in tree:
            if parent not in wrapped:
                continue
            parent = wrapped[parent]
            if children:
                # if no parent is specified, check if any of the messages are
                # uncollapsed, and truncate the thread
                children = [wrapped[child] for child in children
                                           if child in wrapped]
                parent.child = empty_listing()
                # if the parent is new, uncollapsed, or focal we don't
                # want it to become a moremessages wrapper.
                if (self.skip and 
                    not self.parent and not parent.new and parent.is_collapsed 
                    and not (self.focal and self.focal._id == parent._id)):
                    for i, child in enumerate(children):
                        if (child.new or not child.is_collapsed or
                            (self.focal and self.focal._id == child._id)):
                            break
                    else:
                        i = -1
                    parent = Wrapped(MoreMessages(parent, empty_listing()))
                    children = children[i:]

                parent.child.parent_name = parent._fullname
                parent.child.things = []

                for child in children:
                    child.is_child = True
                    if self.focal and child._id == self.focal._id:
                        # focal message is never collapsed
                        child.collapsed = False
                        child.focal = True
                    else:
                        child.collapsed = child.is_collapsed

                    parent.child.things.append(child)
            parent.is_parent = True
            # the parent might be the focal message on a permalink page
            if self.focal and parent._id == self.focal._id:
                parent.collapsed = False
                parent.focal = True
            else:
                parent.collapsed = parent.is_collapsed
            final.append(parent)

        return (final, prev_item, next_item, len(final), len(final))
Esempio n. 17
0
    def get_items(self):
        tree = self.get_tree()
        tree, prev_item, next_item = self._apply_pagination(tree)

        message_ids = []
        for parent_id, child_ids in tree:
            message_ids.append(parent_id)
            message_ids.extend(child_ids)

        if prev_item:
            message_ids.append(prev_item)

        messages = Message._byID(message_ids, data=True, return_dict=False)
        wrapped = {m._id: m for m in self.wrap_items(messages)}

        if prev_item:
            prev_item = wrapped[prev_item]
        if next_item:
            next_item = wrapped[next_item]

        final = []
        for parent_id, child_ids in tree:
            if parent_id not in wrapped:
                continue

            parent = wrapped[parent_id]

            if not self._viewable_message(parent):
                continue

            children = [wrapped[child_id] for child_id in child_ids if child_id in wrapped]

            depth = {parent_id: 0}
            substitute_parents = {}

            if (
                children
                and self.skip
                and not self.threaded
                and not self.parent
                and not parent.new
                and parent.is_collapsed
            ):
                for i, child in enumerate(children):
                    if child.new or not child.is_collapsed:
                        break
                else:
                    i = -1
                # in flat view replace collapsed chain with MoreMessages
                add_child_listing(parent)
                parent = Wrapped(MoreMessages(parent, parent.child))
                children = children[i:]

            for child in sorted(children, key=lambda child: child._id):
                # iterate from the root outwards so we can check the depth
                if self.threaded:
                    try:
                        child_parent = wrapped[child.parent_id]
                    except KeyError:
                        # the stored comment tree was missing this message's
                        # parent, treat it as a top level reply
                        child_parent = parent
                else:
                    # for flat view all messages are decendants of the
                    # parent message
                    child_parent = parent
                parent_depth = depth[child_parent._id]
                child_depth = parent_depth + 1
                depth[child._id] = child_depth

                if child_depth == MAX_RECURSION:
                    # current message is at maximum depth level, all its
                    # children will be displayed as children of its parent
                    substitute_parents[child._id] = child_parent._id

                if child_depth > MAX_RECURSION:
                    child_parent_id = substitute_parents[child.parent_id]
                    substitute_parents[child._id] = child_parent_id
                    child_parent = wrapped[child_parent_id]

                if not hasattr(child_parent, "child"):
                    add_child_listing(child_parent)
                child.is_child = True
                child_parent.child.things.append(child)

            for child in children:
                # look over the children again to decide whether they can be
                # collapsed
                child.threaded = self.threaded
                child.collapsed = self.should_collapse(child)

            if self.threaded and children:
                most_recent_child_id = max(child._id for child in children)
                most_recent_child = wrapped[most_recent_child_id]
                most_recent_child.most_recent = True

            parent.is_parent = True
            parent.threaded = self.threaded
            parent.collapsed = self.should_collapse(parent)
            final.append(parent)

        return (final, prev_item, next_item, len(final), len(final))
Esempio n. 18
0
    def get_items(self):
        tree = self.get_tree()

        prev_item = next_item = None
        if not self.parent:
            if self.num is not None:
                if self.after:
                    if self.reverse:
                        tree = filter(self._tree_filter_reverse, tree)
                        next_item = self.after._id
                        if len(tree) > self.num:
                            first = tree[-(self.num + 1)]
                            prev_item = first[1][-1] if first[1] else first[0]
                            tree = tree[-self.num:]
                    else:
                        prev_item = self.after._id
                        tree = filter(self._tree_filter, tree)
                if len(tree) > self.num:
                    tree = tree[:self.num]
                    last = tree[-1]
                    next_item = last[1][-1] if last[1] else last[0]

        # generate the set of ids to look up and look them up
        message_ids = []
        for root, thread in tree:
            message_ids.append(root)
            message_ids.extend(thread)
        if prev_item:
            message_ids.append(prev_item)

        messages = Message._byID(message_ids, data=True, return_dict=False)
        wrapped = {m._id: m for m in self.wrap_items(messages)}

        if prev_item:
            prev_item = wrapped[prev_item]
        if next_item:
            next_item = wrapped[next_item]

        final = []
        for parent, children in tree:
            if parent not in wrapped:
                continue

            parent = wrapped[parent]

            if not self._viewable_message(parent):
                continue

            if children:
                # if no parent is specified, check if any of the messages are
                # uncollapsed, and truncate the thread
                children = [
                    wrapped[child] for child in children if child in wrapped
                ]
                add_child_listing(parent)
                # if the parent is new, uncollapsed, or focal we don't
                # want it to become a moremessages wrapper.
                if (self.skip and not self.parent and not parent.new
                        and parent.is_collapsed
                        and not (self.focal and self.focal._id == parent._id)):
                    for i, child in enumerate(children):
                        if (child.new or not child.is_collapsed or
                            (self.focal and self.focal._id == child._id)):
                            break
                    else:
                        i = -1
                    parent = Wrapped(MoreMessages(parent, parent.child))
                    children = children[i:]

                parent.child.parent_name = parent._fullname
                parent.child.things = []

                for child in children:
                    child.is_child = True
                    if self.focal and child._id == self.focal._id:
                        # focal message is never collapsed
                        child.collapsed = False
                        child.focal = True
                    else:
                        child.collapsed = child.is_collapsed

                    parent.child.things.append(child)
            parent.is_parent = True
            # the parent might be the focal message on a permalink page
            if self.focal and parent._id == self.focal._id:
                parent.collapsed = False
                parent.focal = True
            else:
                parent.collapsed = parent.is_collapsed
            final.append(parent)

        return (final, prev_item, next_item, len(final), len(final))
Esempio n. 19
0
    def modmail_event(self, message, request=None, context=None):
        """Create a 'modmail' event for event-collector.

        message: An r2.models.Message object
        request: pylons.request of the request that created the message
        context: pylons.tmpl_context of the request that created the message

        """

        from r2.models import Account, Message

        sender = message.author_slow
        sr = message.subreddit_slow
        sender_is_moderator = sr.is_moderator_with_perms(sender, "mail")

        if message.first_message:
            first_message = Message._byID(message.first_message, data=True)
        else:
            first_message = message

        event = EventV2(
            topic="message_events",
            event_type="ss.send_message",
            time=message._date,
            request=request,
            context=context,
            data={
                # set these manually rather than allowing them to be set from
                # the request context because the loggedin user might not
                # be the message sender
                "user_id": sender._id,
                "user_name": sender.name,
            },
        )

        if sender == Account.system_user():
            sender_type = "automated"
        elif sender_is_moderator:
            sender_type = "moderator"
        else:
            sender_type = "user"

        event.add("sender_type", sender_type)
        event.add("sr_id", sr._id)
        event.add("sr_name", sr.name)
        event.add("message_id", message._id)
        event.add("message_fullname", message._fullname)
        event.add("first_message_id", first_message._id)
        event.add("first_message_fullname", first_message._fullname)

        if request and request.POST.get("source", None):
            source = request.POST["source"]
            if source in {"compose", "permalink", "modmail", "usermail"}:
                event.add("page", source)

        if message.sent_via_email:
            event.add("is_third_party", True)
            event.add("third_party_metadata", "mailgun")

        if not message.to_id:
            target = sr
        else:
            target = Account._byID(message.to_id, data=True)

        event.add_target_fields(target)

        self.save_event(event)
Esempio n. 20
0
    def message_event(self, message, request=None, context=None):
        """Create a 'message' event for event-collector.

        message: An r2.models.Message object
        request: pylons.request of the request that created the message
        context: pylons.tmpl_context of the request that created the message

        """

        from r2.models import Account, Message

        sender = message.author_slow

        if message.first_message:
            first_message = Message._byID(message.first_message, data=True)
        else:
            first_message = message

        event = Event(
            topic="message_events",
            event_type="ss.send_message",
            time=message._date,
            request=request,
            context=context,
            data={
                # set these manually rather than allowing them to be set from
                # the request context because the loggedin user might not
                # be the message sender
                "user_id": sender._id,
                "user_name": sender.name,
            },
        )

        if sender == Account.system_user():
            sender_type = "automated"
        else:
            sender_type = "user"

        event.add("sender_type", sender_type)
        event.add("message_kind", "message")
        event.add("message_id", message._id)
        event.add("message_fullname", message._fullname)

        event.add_text("message_body", message.body)
        event.add_text("message_subject", message.subject)

        event.add("first_message_id", first_message._id)
        event.add("first_message_fullname", first_message._fullname)

        if request and request.POST.get("source", None):
            source = request.POST["source"]
            if source in {"compose", "permalink", "usermail"}:
                event.add("page", source)

        if message.sent_via_email:
            event.add("is_third_party", True)
            event.add("third_party_metadata", "mailgun")

        target = Account._byID(message.to_id, data=True)

        event.add_target_fields(target)

        self.save_event(event)
Esempio n. 21
0
    def POST_mod_messages(self, conversation, msg_body, is_author_hidden,
                          is_internal):
        """Creates a new message for a particular ModmailConversation

        URL Params:
        conversation_id -- id of the conversation to post a new message to

        POST Params:
        body            -- this is the message body
        isAuthorHidden  -- boolean on whether to hide author, i.e. respond as
                           the subreddit
        isInternal      -- boolean to signify a moderator only message
        """
        self._validate_vmodconversation()

        sr = Subreddit._by_fullname(conversation.owner_fullname)
        self._feature_enabled_check(sr)

        # make sure the user is not muted before posting a message
        if sr.is_muted(c.user):
            return self.send_error(400, errors.USER_MUTED)

        if conversation.is_internal and not is_internal:
            is_internal = True

        is_mod = sr.is_moderator(c.user)
        if not is_mod and is_author_hidden:
            return self.send_error(
                403,
                errors.MOD_REQUIRED,
                fields='isAuthorHidden',
            )
        elif not is_mod and is_internal:
            return self.send_error(
                403,
                errors.MOD_REQUIRED,
                fields='isInternal',
            )

        try:
            if not conversation.is_internal and not conversation.is_auto:
                participant = conversation.get_participant_account()

                if participant and sr.is_muted(participant):
                    return self.send_error(
                        400,
                        errors.MUTED_FROM_SUBREDDIT,
                    )
        except NotFound:
            pass

        try:
            new_message = conversation.add_message(
                c.user,
                msg_body,
                is_author_hidden=is_author_hidden,
                is_internal=is_internal,
            )
        except:
            return self.send_error(500, errors.MODMAIL_MESSAGE_NOT_SAVED)

        # Add the message to the legacy messaging system as well (unless it's
        # an internal message on a non-internal conversation, since we have no
        # way to hide specific messages from the external participant)
        legacy_incompatible = is_internal and not conversation.is_internal
        if (conversation.legacy_first_message_id and not legacy_incompatible):
            first_message = Message._byID(conversation.legacy_first_message_id)
            subject = conversation.subject
            if not subject.startswith('re: '):
                subject = 're: ' + subject

            # Retrieve the participant to decide whether to send the message
            # to the sr or to the participant. If the currently logged in user
            # is the same as the participant then address the message to the
            # sr.
            recipient = sr
            if not is_internal:
                try:
                    participant = (
                        ModmailConversationParticipant.get_participant(
                            conversation.id))

                    is_participant = (
                        (c.user._id == participant.account_id)
                        and not sr.is_moderator_with_perms(c.user, 'mail'))

                    if not is_participant:
                        recipient = Account._byID(participant.account_id)
                except NotFound:
                    pass

            message, inbox_rel = Message._new(
                c.user,
                recipient,
                subject,
                msg_body,
                request.ip,
                parent=first_message,
                from_sr=is_author_hidden,
                create_modmail=False,
            )
            queries.new_message(message, inbox_rel)

        serializable_convo = conversation.to_serializable(
            entity=sr,
            all_messages=True,
            current_user=c.user,
        )
        messages = serializable_convo.pop('messages')

        g.events.new_modmail_event(
            'ss.send_modmail_message',
            conversation,
            message=new_message,
            msg_author=c.user,
            sr=sr,
            request=request,
            context=c,
        )

        response.status_code = 201
        return simplejson.dumps({
            'conversation': serializable_convo,
            'messages': messages,
        })