Removes trailing spaces.
[platal.git] / bin / newsletter.bounces.processor.py
index e3972d6..8fc3c1a 100755 (executable)
@@ -1,7 +1,7 @@
 #!/usr/bin/env python2.5
 # -*- coding: utf-8 -*-
 #***************************************************************************
-#*  Copyright (C) 2004-2008 polytechnique.org                              *
+#*  Copyright (C) 2003-2010 Polytechnique.org                              *
 #*  http://opensource.polytechnique.org/                                   *
 #*                                                                         *
 #*  This program is free software; you can redistribute it and/or modify   *
@@ -20,8 +20,6 @@
 #*  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA                *
 #***************************************************************************
 
-# Copyright (c) 2008 Aymeric Augustin
-
 """
 Process as automatically as possible bounces from the newsletter
 
@@ -127,16 +125,18 @@ def findAddressInBounce(bounce):
         print '! Not a valid bounce (expected multipart/report, found %s).' % bounce.get_content_type()
         return None
     # Extract the second component of the multipart/report
-    if len(bounce.get_payload()) < 2:
-        print '! Not a valid bounce (expected at least 2 parts, found %d).' % len(bounce)
+    num_payloads = len(bounce.get_payload())
+    if num_payloads < 2:
+        print '! Not a valid bounce (expected at least 2 parts, found %d).' % num_payloads
         return None
     status = bounce.get_payload(1)
     if status.get_content_type() != 'message/delivery-status':
         print '! Not a valid bounce (expected message/delivery-status, found %s).' % bounce.get_content_type()
         return None
     # The per-message-fields don't matter here, get only the per-recipient-fields
-    if len(status.get_payload()) < 2:
-        print '! Not a valid bounce (expected at least 2 parts, found %d).' % len(status)
+    num_payloads = len(status.get_payload())
+    if num_payloads < 2:
+        print '! Not a valid bounce (expected at least 2 parts, found %d).' % num_payloads
         return None
     content = status.get_payload(1)
     if content.get_content_type() != 'text/plain':
@@ -176,15 +176,24 @@ class DirectBouncesFilter(MboxFilter):
         if message['X-Spam-Flag'] is None:
             # During finalization, we will verifiy that all messages were processed
             self.seen += 1
+            # Special case: ignore mailman notifications for the mailing-list
+            # on which the NL is forwarded
+            if message['From'] == 'polytechnique.org_newsletter-externes-bounces@listes.polytechnique.org':
+                print '! Dropping a notification from mailman for newsletter-externes@polytechnique.org, this should be OK.'
+                self.seen -= 1
+                return True
             # Additionnal checks, just to be sure
-            if message['From'] != 'MAILER-DAEMON@polytechnique.org (Mail Delivery System)' \
+            elif message['From'] != 'MAILER-DAEMON@polytechnique.org (Mail Delivery System)' \
             or message['Subject'] != 'Undelivered Mail Returned to Sender':
-                return False
-            email = findAddressInBounce(message)
-            if email is not None:
-                self.emails.append(email)
-                self.mbox.add(message)
-                return True
+                print '! Not an usual direct bounce (From="%s", Subject="%s").' % (message['From'], message['Subject'])
+            else:
+                email = findAddressInBounce(message)
+                if email is not None:
+                    self.emails.append(email)
+                    self.mbox.add(message)
+                    return True
+                else:
+                    print '! No email found in direct bounce, this is really bad.'
         return False
 
     def finalize(self):
@@ -211,7 +220,8 @@ class SpamFilter(MboxFilter):
         self.mbox.clear()
 
     def process(self, message):
-        if message['X-Spam-Flag'].startswith('Yes, tests=bogofilter'):
+        if message['X-Spam-Flag'] is not None \
+        and message['X-Spam-Flag'].startswith('Yes, tests=bogofilter'):
             self.mbox.add(message)
             return True
         return False
@@ -232,7 +242,8 @@ class UnsureFilter(MboxFilter):
         self.mbox.clear()
 
     def process(self, message):
-        if message['X-Spam-Flag'].startswith('Unsure, tests=bogofilter'):
+        if message['X-Spam-Flag'] is not None \
+        and message['X-Spam-Flag'].startswith('Unsure, tests=bogofilter'):
             self.mbox.add(message)
             return True
         return False
@@ -251,13 +262,14 @@ class CheckNonSpamFilter(MboxFilter):
         self.seen = 0
 
     def process(self, message):
-        if not message['X-Spam-Flag'].startswith('No, tests=bogofilter'):
+        if message['X-Spam-Flag'] is None \
+        or not message['X-Spam-Flag'].startswith('No, tests=bogofilter'):
             self.seen += 1
         return False
 
     def finalize(self):
         if self.seen > 0:
-            print 'Encountered %d messages that were neither spam, nor unsure, nor non-spams.' % self.counter
+            print 'Encountered %d messages that were neither spam, nor unsure, nor non-spams.' % self.seen
             print 'Please investigate.'
         else:
             print 'All messages were either spam, or unsure, or non-spams. Good.'
@@ -272,10 +284,14 @@ class OutOfOfficeFilter(MboxFilter):
         self.mbox.clear()
         subject_re = [
             r'^Absen(t|ce)',
-            r'^Out of office',
-            r'est absent',
+            r'(est|is) absent',
+            r'^Out of (the )?office',
             r'is out of (the )?office',
-            u'^RĂ©ponse automatique d\'absence du bureau', # unicode!
+            r'I am out of town',
+            r'automatique d\'absence',
+            r'Notification d\'absence'
+            u'RĂ©ponse automatique :', #unicode!
+            r'AutoReply',
         ]
         self.subject_regexes = map(re.compile, subject_re, [re.I | re.U] * len(subject_re))
 
@@ -301,21 +317,9 @@ class DeliveryStatusNotificationFilter(MboxFilter):
         self.mbox_file = '%s.dsn' % mbox_file
         self.mbox = mailbox.mbox(self.mbox_file)
         self.mbox.clear()
-        subject_re = [
-            r'^DELIVERY FAILURE: ',
-            r'^Delivery Notification: Delivery has failed$',
-            r'^Delivery Status Notification ?\(Failure\)$',
-            r'^Mail delivery failed',
-            r'^(Mail revenu en erreur / )?Undelivered Mail Returned to Sender$',
-            r'^Returned mail: see transcript for details$',
-            r'^Undeliverable( mail)?:',
-            r'^Undelivered Mail Returned to Sender$',
-        ]
-        self.subject_regexes = map(re.compile, subject_re, [re.I | re.U] * len(subject_re))
 
     def process(self, message):
-        subject = findSubject(message)
-        if subject is not None and any(regex.search(subject) for regex in self.subject_regexes):
+        if message.get_content_type() == 'multipart/report':
             email = findAddressInBounce(message)
             if email is not None:
                 self.emails.append(email)