summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbwarsaw2007-05-31 05:01:00 +0000
committerbwarsaw2007-05-31 05:01:00 +0000
commitd0bd1d07a472db4b897ddf81c04ae3602cba9297 (patch)
treeeaa63652e7bd0a82a57443f9c89746ae254027bd
parent8c1fa18658dc730b53ddbb4766549a4fe595e453 (diff)
downloadmailman-d0bd1d07a472db4b897ddf81c04ae3602cba9297.tar.gz
mailman-d0bd1d07a472db4b897ddf81c04ae3602cba9297.tar.zst
mailman-d0bd1d07a472db4b897ddf81c04ae3602cba9297.zip
-rw-r--r--Mailman/Handlers/Replybot.py41
-rw-r--r--Mailman/docs/decorate.txt4
-rw-r--r--Mailman/docs/replybot.txt230
-rw-r--r--Mailman/testing/test_handlers.py7
-rw-r--r--Mailman/testing/test_replybot.py32
5 files changed, 277 insertions, 37 deletions
diff --git a/Mailman/Handlers/Replybot.py b/Mailman/Handlers/Replybot.py
index d63169eb2..18fc83ced 100644
--- a/Mailman/Handlers/Replybot.py
+++ b/Mailman/Handlers/Replybot.py
@@ -20,13 +20,16 @@
import time
import logging
+from string import Template
+
from Mailman import Message
from Mailman import Utils
from Mailman.i18n import _
-from Mailman.SafeDict import SafeDict
log = logging.getLogger('mailman.error')
+__i18n_templates__ = True
+
def process(mlist, msg, msgdata):
@@ -69,42 +72,26 @@ def process(mlist, msg, msgdata):
quiet_until = mlist.postings_responses.get(sender, 0)
if quiet_until > now:
return
- #
# Okay, we know we're going to auto-respond to this sender, craft the
# message, send it, and update the database.
realname = mlist.real_name
subject = _(
- 'Auto-response for your message to the "%(realname)s" mailing list')
- # Do string interpolation
- d = SafeDict({'listname' : realname,
- 'listurl' : mlist.GetScriptURL('listinfo'),
- 'requestemail': mlist.GetRequestEmail(),
- # BAW: Deprecate adminemail; it's not advertised but still
- # supported for backwards compatibility.
- 'adminemail' : mlist.GetBouncesEmail(),
- 'owneremail' : mlist.GetOwnerEmail(),
- })
- # Just because we're using a SafeDict doesn't mean we can't get all sorts
- # of other exceptions from the string interpolation. Let's be ultra
- # conservative here.
+ 'Auto-response for your message to the "$realname" mailing list')
+ # Do string interpolation into the autoresponse text
+ d = dict(listname = realname,
+ listurl = mlist.GetScriptURL('listinfo'),
+ requestemail = mlist.request_address,
+ owneremail = mlist.owner_address,
+ )
if toadmin:
rtext = mlist.autoresponse_admin_text
elif torequest:
rtext = mlist.autoresponse_request_text
else:
rtext = mlist.autoresponse_postings_text
- # Using $-strings?
- if getattr(mlist, 'use_dollar_strings', 0):
- rtext = Utils.to_percent(rtext)
- try:
- text = rtext % d
- except Exception:
- log.error('Bad autoreply text for list: %s\n%s',
- mlist.internal_name(), rtext)
- text = rtext
- # Wrap the response.
- text = Utils.wrap(text)
- outmsg = Message.UserNotification(sender, mlist.GetBouncesEmail(),
+ # Interpolation and Wrap the response text.
+ text = Utils.wrap(Template(rtext).safe_substitute(d))
+ outmsg = Message.UserNotification(sender, mlist.bounces_address,
subject, text, mlist.preferred_language)
outmsg['X-Mailer'] = _('The Mailman Replybot')
# prevent recursions and mail loops!
diff --git a/Mailman/docs/decorate.txt b/Mailman/docs/decorate.txt
index 1325eb676..fc2f9a9f3 100644
--- a/Mailman/docs/decorate.txt
+++ b/Mailman/docs/decorate.txt
@@ -10,6 +10,7 @@ of the mailing list and the type of message being processed.
>>> from Mailman.configuration import config
>>> from Mailman.database import flush
>>> mlist = config.list_manager.create('_xtest@example.com')
+ >>> flush()
>>> msg_text = """\
... From: aperson@example.org
...
@@ -66,9 +67,6 @@ header and footer for information to be filled in with mailing list specific
data. An example of such information is the mailing list's "real name" (a
short descriptive name for the mailing list).
- # XXX Remove this line after converting this test
- >>> mlist.use_dollar_strings = True
-
>>> msg = message_from_string(msg_text)
>>> mlist.msg_header = '$real_name header\n'
>>> mlist.msg_footer = '$real_name footer'
diff --git a/Mailman/docs/replybot.txt b/Mailman/docs/replybot.txt
new file mode 100644
index 000000000..5315eba70
--- /dev/null
+++ b/Mailman/docs/replybot.txt
@@ -0,0 +1,230 @@
+Auto-reply handler
+==================
+
+Mailman has an auto-reply handler that sends automatic responses to messages
+it receives on its posting address, or special robot addresses. Automatic
+responses are subject to various conditions, such as headers in the original
+message or the amount of time since the last auto-response.
+
+ >>> from email import message_from_string
+ >>> from Mailman.Handlers.Replybot import process
+ >>> from Mailman.Message import Message
+ >>> from Mailman.configuration import config
+ >>> from Mailman.database import flush
+ >>> mlist = config.list_manager.create('_xtest@example.com')
+ >>> mlist.real_name = 'XTest'
+ >>> flush()
+
+ >>> # Ensure that the virgin queue is empty, since we'll be checking this
+ >>> # for new auto-response messages.
+ >>> from Mailman.Queue.sbcache import get_switchboard
+ >>> virginq = get_switchboard(config.VIRGINQUEUE_DIR)
+ >>> virginq.files()
+ []
+
+
+Basic autoresponding
+--------------------
+
+Basic autoresponding occurs when the list is set up to respond to either its
+-owner address, its -request address, or to the posting address, and a message
+is sent to one of these addresses. A mailing list also has an autoresponse
+grace period which describes how much time must pass before a second response
+will be sent, with 0 meaning "there is no grace period".
+
+ >>> mlist.autorespond_admin = True
+ >>> mlist.autoresponse_graceperiod = 0
+ >>> mlist.autoresponse_admin_text = 'admin autoresponse text'
+ >>> flush()
+ >>> msg = message_from_string("""\
+ ... From: aperson@example.com
+ ... To: _xtest-owner@example.com
+ ...
+ ... help
+ ... """, Message)
+ >>> process(mlist, msg, dict(toowner=True))
+ >>> len(virginq.files())
+ 1
+ >>> qmsg, qdata = virginq.dequeue(virginq.files()[0])
+ >>> # Print only some of the meta data. The rest is uninteresting.
+ >>> qdata['listname']
+ '_xtest@example.com'
+ >>> sorted(qdata['recips'])
+ ['aperson@example.com']
+ >>> # Delete data that is time dependent or random
+ >>> del qmsg['message-id']
+ >>> del qmsg['date']
+ >>> print qmsg.as_string()
+ MIME-Version: 1.0
+ Content-Type: text/plain; charset="us-ascii"
+ Content-Transfer-Encoding: 7bit
+ Subject: Auto-response for your message to the "XTest" mailing list
+ From: _xtest-bounces@example.com
+ To: aperson@example.com
+ X-Mailer: The Mailman Replybot
+ X-Ack: No
+ Precedence: bulk
+ <BLANKLINE>
+ admin autoresponse text
+ >>> virginq.files()
+ []
+
+
+Short circuiting
+----------------
+
+Several headers in the original message determine whether an autoresponse
+should even be sent. For example, if the message has an "X-Ack: No" header,
+no auto-response is sent.
+
+ >>> msg = message_from_string("""\
+ ... From: aperson@example.com
+ ... X-Ack: No
+ ...
+ ... help me
+ ... """, Message)
+ >>> process(mlist, msg, dict(toowner=True))
+ >>> virginq.files()
+ []
+
+Mailman itself can suppress autoresponses for certain types of internally
+crafted messages, by setting the 'noack' metadata key.
+
+ >>> msg = message_from_string("""\
+ ... From: mailman@example.com
+ ...
+ ... help for you
+ ... """, Message)
+ >>> process(mlist, msg, dict(noack=True, toowner=True))
+ >>> virginq.files()
+ []
+
+If there is a Precedence: header with any of the values 'bulk', 'junk', or
+'list', then the autoresponse is also suppressed.
+
+ >>> msg = message_from_string("""\
+ ... From: asystem@example.com
+ ... Precedence: bulk
+ ...
+ ... hey!
+ ... """, Message)
+ >>> process(mlist, msg, dict(toowner=True))
+ >>> virginq.files()
+ []
+
+ >>> msg.replace_header('precedence', 'junk')
+ >>> process(mlist, msg, dict(toowner=True))
+ >>> virginq.files()
+ []
+ >>> msg.replace_header('precedence', 'list')
+ >>> process(mlist, msg, dict(toowner=True))
+ >>> virginq.files()
+ []
+
+Unless the X-Ack: header has a value of "yes", in which case, the Precedence
+header is ignored.
+
+ >>> msg['X-Ack'] = 'yes'
+ >>> process(mlist, msg, dict(toowner=True))
+ >>> len(virginq.files())
+ 1
+ >>> qmsg, qdata = virginq.dequeue(virginq.files()[0])
+ >>> del qmsg['message-id']
+ >>> del qmsg['date']
+ >>> print qmsg.as_string()
+ MIME-Version: 1.0
+ Content-Type: text/plain; charset="us-ascii"
+ Content-Transfer-Encoding: 7bit
+ Subject: Auto-response for your message to the "XTest" mailing list
+ From: _xtest-bounces@example.com
+ To: asystem@example.com
+ X-Mailer: The Mailman Replybot
+ X-Ack: No
+ Precedence: bulk
+ <BLANKLINE>
+ admin autoresponse text
+
+
+Available auto-responses
+------------------------
+
+As shown above, a message sent to the -owner address will get an auto-response
+with the text set for owner responses. Two other types of email will get
+auto-responses: those sent to the -request address...
+
+ >>> mlist.autorespond_requests = True
+ >>> mlist.autoresponse_request_text = 'robot autoresponse text'
+ >>> flush()
+ >>> msg = message_from_string("""\
+ ... From: aperson@example.com
+ ... To: _xtest-request@example.com
+ ...
+ ... help me
+ ... """, Message)
+ >>> process(mlist, msg, dict(torequest=True))
+ >>> len(virginq.files())
+ 1
+ >>> qmsg, qdata = virginq.dequeue(virginq.files()[0])
+ >>> del qmsg['message-id']
+ >>> del qmsg['date']
+ >>> print qmsg.as_string()
+ MIME-Version: 1.0
+ Content-Type: text/plain; charset="us-ascii"
+ Content-Transfer-Encoding: 7bit
+ Subject: Auto-response for your message to the "XTest" mailing list
+ From: _xtest-bounces@example.com
+ To: aperson@example.com
+ X-Mailer: The Mailman Replybot
+ X-Ack: No
+ Precedence: bulk
+ <BLANKLINE>
+ robot autoresponse text
+
+...and those sent to the posting address.
+
+ >>> mlist.autorespond_postings = True
+ >>> mlist.autoresponse_postings_text = 'postings autoresponse text'
+ >>> flush()
+ >>> msg = message_from_string("""\
+ ... From: aperson@example.com
+ ... To: _xtest@example.com
+ ...
+ ... help me
+ ... """, Message)
+ >>> process(mlist, msg, {})
+ >>> len(virginq.files())
+ 1
+ >>> qmsg, qdata = virginq.dequeue(virginq.files()[0])
+ >>> del qmsg['message-id']
+ >>> del qmsg['date']
+ >>> print qmsg.as_string()
+ MIME-Version: 1.0
+ Content-Type: text/plain; charset="us-ascii"
+ Content-Transfer-Encoding: 7bit
+ Subject: Auto-response for your message to the "XTest" mailing list
+ From: _xtest-bounces@example.com
+ To: aperson@example.com
+ X-Mailer: The Mailman Replybot
+ X-Ack: No
+ Precedence: bulk
+ <BLANKLINE>
+ postings autoresponse text
+
+
+Grace periods
+-------------
+
+Auto-responses have a grace period, during which no additional responses will
+be sent. This is so as not to bombard the sender with responses. The grace
+period is measured in days.
+
+XXX Add grace period tests.
+
+
+Clean up
+--------
+
+ >>> config.list_manager.delete(mlist)
+ >>> flush()
+ >>> [name for name in config.list_manager.names]
+ []
diff --git a/Mailman/testing/test_handlers.py b/Mailman/testing/test_handlers.py
index 985002189..f4ad2ba4f 100644
--- a/Mailman/testing/test_handlers.py
+++ b/Mailman/testing/test_handlers.py
@@ -46,7 +46,6 @@ from Mailman.Handlers import FileRecips
from Mailman.Handlers import Hold
from Mailman.Handlers import MimeDel
from Mailman.Handlers import Moderate
-from Mailman.Handlers import Replybot
from Mailman.Handlers import Scrubber
# Don't test handlers such as SMTPDirect and Sendmail here
from Mailman.Handlers import SpamDetect
@@ -1205,11 +1204,6 @@ class TestModerate(TestBase):
-class TestReplybot(TestBase):
- pass
-
-
-
class TestScrubber(TestBase):
def test_save_attachment(self):
mlist = self._mlist
@@ -1716,7 +1710,6 @@ def test_suite():
suite.addTest(unittest.makeSuite(TestHold))
suite.addTest(unittest.makeSuite(TestMimeDel))
suite.addTest(unittest.makeSuite(TestModerate))
- suite.addTest(unittest.makeSuite(TestReplybot))
suite.addTest(unittest.makeSuite(TestScrubber))
suite.addTest(unittest.makeSuite(TestSpamDetect))
suite.addTest(unittest.makeSuite(TestTagger))
diff --git a/Mailman/testing/test_replybot.py b/Mailman/testing/test_replybot.py
new file mode 100644
index 000000000..6b371c9d0
--- /dev/null
+++ b/Mailman/testing/test_replybot.py
@@ -0,0 +1,32 @@
+# Copyright (C) 2007 by the Free Software Foundation, Inc.
+#
+# This program 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 2
+# of the License, or (at your option) any later version.
+#
+# This program 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 this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
+
+"""Doctest harness for testing the replybot handler."""
+
+import doctest
+import unittest
+
+options = (doctest.ELLIPSIS
+ | doctest.NORMALIZE_WHITESPACE
+ | doctest.REPORT_NDIFF)
+
+
+def test_suite():
+ suite = unittest.TestSuite()
+ suite.addTest(doctest.DocFileSuite('../docs/replybot.txt',
+ optionflags=options))
+ return suite