replybot.py 4.26 KB
Newer Older
Barry Warsaw's avatar
Barry Warsaw committed
1
# Copyright (C) 2007-2008 by the Free Software Foundation, Inc.
2
#
Barry Warsaw's avatar
Barry Warsaw committed
3
# This file is part of GNU Mailman.
4
#
Barry Warsaw's avatar
Barry Warsaw committed
5 6 7 8
# 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.
9
#
Barry Warsaw's avatar
Barry Warsaw committed
10 11 12 13 14 15 16
# 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/>.
17 18 19 20 21 22 23 24 25

"""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).

__all__ = [
    'autorespond_to_sender',
26
    'can_acknowledge',
27 28 29 30 31
    ]

import logging
import datetime

32 33
from mailman import Utils
from mailman import i18n
34
from mailman.config import config
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


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



def autorespond_to_sender(mlist, sender, lang=None):
    """Return True if Mailman should auto-respond to this sender.

    This is only consulted for messages sent to the -request address, or
    for posting hold notifications, and serves only as a safety value for
    mail loops with email 'bots.
    """
    if lang is None:
        lang = mlist.preferred_language
    if config.MAX_AUTORESPONSES_PER_DAY == 0:
        # Unlimited.
        return True
    today = datetime.date.today()
    info = mlist.hold_and_cmd_autoresponses.get(sender)
    if info is None or info[0] <> today:
        # This is the first time we've seen a -request/post-hold for this
        # sender today.
        mlist.hold_and_cmd_autoresponses[sender] = (today, 1)
        return True
    date, count = info
    if count < 0:
        # They've already hit the limit for today, and we've already notified
        # them of this fact, so there's nothing more to do.
        log.info('-request/hold autoresponse discarded for: %s', sender)
        return False
    if count >= config.MAX_AUTORESPONSES_PER_DAY:
        log.info('-request/hold autoresponse limit hit for: %s', sender)
        mlist.hold_and_cmd_autoresponses[sender] = (today, -1)
        # 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):
            msg = Message.UserNotification(
                sender, mlist.owner_address,
                _('Last autoresponse notification for today'),
                text, lang=lang)
        msg.send(mlist)
        return False
    mlist.hold_and_cmd_autoresponses[sender] = (today, count + 1)
    return True

89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109


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.
110
    for header in msg.keys():
111 112 113 114 115 116
        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
117
    if msg.get('auto-submitted', 'no').lower() <> 'no':
118 119 120 121
        return False
    if msg.get('return-path') == '<>':
        return False
    return True