summaryrefslogtreecommitdiff
path: root/src/mailman/app/replybot.py
blob: fa423973b7f4edd6a8bfd6a4d70e95d37fea1b8f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# Copyright (C) 2007-2009 by the Free Software Foundation, Inc.
#
# This file is part of GNU Mailman.
#
# GNU Mailman is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# GNU Mailman.  If not, see <http://www.gnu.org/licenses/>.

"""Application level auto-reply code."""

# XXX This should undergo a rewrite to move this functionality off of the
# mailing list.  The reply governor should really apply site-wide per
# recipient (I think).

from __future__ import unicode_literals

__metaclass__ = type
__all__ = [
    'autorespond_to_sender',
    'can_acknowledge',
    ]

import logging
import datetime

from mailman import Utils
from mailman import i18n
from mailman.config import config
from mailman.utilities.datetime import today
from mailman.interfaces.autorespond import IAutoResponseSet, Response


log = logging.getLogger('mailman.vette')
_ = i18n._



def autorespond_to_sender(mlist, sender, response_type, lang=None):
    """Should Mailman automatically respond to this sender?

    :param mlist: The mailing list.
    :type mlist: `IMailingList`.
    :param sender: The sender's email address.
    :type sender: string
    :param response_type: The type of response that might be sent.
    :type response_type: `Response` enum
    :param lang: Optional language.
    :type lang: `ILanguage` or None
    :return: True if an automatic response should be sent, otherwise False.
        If an automatic response is not sent, a message is sent indicating
        that, er no more will be sent today.
    :rtype: bool
    """
    if lang is None:
        lang = mlist.preferred_language
    max_autoresponses_per_day = int(config.mta.max_autoresponses_per_day)
    if max_autoresponses_per_day == 0:
        # Unlimited.
        return True
    # Get an IAddress from an email address.
    address = config.db.user_manager.get_address(sender)
    if address is None:
        address = config.db.user_manager.create_address(sender)
    response_set = IAutoResponseSet(mlist)
    todays_count = response_set.todays_count(address, response_type)
    if todays_count < max_autoresponses_per_day:
        # This person has not reached their automatic response limit, so it's
        # okay to send a response.
        response_set.response_sent(address, response_type)
        return True
    elif todays_count == max_autoresponses_per_day:
        # The last one we sent was the last one we should send today.  Instead
        # of sending an automatic response, send them the "no more today"
        # message.
        log.info('-request/hold autoresponse limit hit (%s): %s',
                 response_type, sender)
        response_set.response_sent(address, response_type)
        # Send this notification message instead.
        text = Utils.maketext(
            'nomoretoday.txt',
            {'sender' : sender,
             'listname': mlist.fqdn_listname,
             'num' : count,
             'owneremail': mlist.owner_address,
             },
            lang=lang)
        with i18n.using_language(lang.code):
            msg = Message.UserNotification(
                sender, mlist.owner_address,
                _('Last autoresponse notification for today'),
                text, lang=lang)
        msg.send(mlist)
        return False
    else:
        # We've sent them everything we're going to send them today.
        log.info('Automatic response limit discard (%s): %s',
                 response_type, sender)
        return False



def can_acknowledge(msg):
    """A boolean specifying whether this message can be acknowledged.

    There are several reasons why a message should not be acknowledged, mostly
    related to competing standards or common practices.  These include:

    * The message has a X-No-Ack header with any value
    * The message has an X-Ack header with a 'no' value
    * The message has a Precedence header
    * The message has an Auto-Submitted header and that header does not have a
      value of 'no'
    * The message has an empty Return-Path header, e.g. <>
    * The message has any RFC 2369 headers (i.e. List-* headers)

    :param msg: a Message object.
    :return: Boolean specifying whether the message can be acknowledged or not
        (which is different from whether it will be acknowledged).
    """
    # I wrote it this way for clarity and consistency with the docstring.
    for header in msg.keys():
        if header in ('x-no-ack', 'precedence'):
            return False
        if header.lower().startswith('list-'):
            return False
    if msg.get('x-ack', '').lower() == 'no':
        return False
    if msg.get('auto-submitted', 'no').lower() <> 'no':
        return False
    if msg.get('return-path') == '<>':
        return False
    return True