Code

Added commentage to the dbinit files to help people with their
[roundup.git] / roundup / mailgw.py
index b3bda148934ca24c3ed115e77c1d1f3482219db1..4c99afb0a22de18a65b7283dbb4032c2a92a10e6 100644 (file)
@@ -73,7 +73,7 @@ are calling the create() method to create a new node). If an auditor raises
 an exception, the original message is bounced back to the sender with the
 explanatory message given in the exception. 
 
-$Id: mailgw.py,v 1.64 2002-02-14 23:46:02 richard Exp $
+$Id: mailgw.py,v 1.73 2002-05-22 04:12:05 richard Exp $
 '''
 
 
@@ -120,7 +120,7 @@ class Message(mimetools.Message):
         return Message(s)
 
 subject_re = re.compile(r'(?P<refwd>\s*\W?\s*(fwd|re|aw)\s*\W?\s*)*'
-    r'\s*(\[(?P<classname>[^\d\s]+)(?P<nodeid>\d+)?\])'
+    r'\s*(\[(?P<classname>[^\d\s]+)(?P<nodeid>\d+)?\])?'
     r'\s*(?P<title>[^[]+)?(\[(?P<args>.+?)\])?', re.I)
 
 class MailGW:
@@ -131,7 +131,7 @@ class MailGW:
     def main(self, fp):
         ''' fp - the file from which to read the Message.
         '''
-        self.handle_Message(Message(fp))
+        return self.handle_Message(Message(fp))
 
     def handle_Message(self, message):
         '''Handle an RFC822 Message
@@ -292,6 +292,20 @@ class MailGW:
             raise MailUsageHelp
 
         m = subject_re.match(subject)
+
+        # check for well-formed subject line
+        if m:
+            # get the classname
+            classname = m.group('classname')
+            if classname is None:
+                # no classname, fallback on the default
+                if hasattr(self.instance, 'MAIL_DEFAULT_CLASS') and \
+                        self.instance.MAIL_DEFAULT_CLASS:
+                    classname = self.instance.MAIL_DEFAULT_CLASS
+                else:
+                    # fail
+                    m = None
+
         if not m:
             raise MailUsageError, '''
 The message you sent to roundup did not contain a properly formed subject
@@ -307,8 +321,7 @@ line. The subject must contain a class name or designator to indicate the
 Subject was: "%s"
 '''%subject
 
-        # get the classname
-        classname = m.group('classname')
+        # get the class
         try:
             cl = self.db.getclass(classname)
         except KeyError:
@@ -480,7 +493,14 @@ Unknown address: %s
             r = recipient[1].strip().lower()
             if r == tracker_email or not r:
                 continue
-            recipients.append(self.db.uidFromAddress(recipient))
+
+            # look up the recipient - create if necessary (and we're
+            # allowed to)
+            recipient = self.db.uidFromAddress(recipient, create)
+
+            # if all's well, add the recipient to the list
+            if recipient:
+                recipients.append(recipient)
 
         #
         # handle message-id and in-reply-to
@@ -510,8 +530,8 @@ Unknown address: %s
         #   required body parts.
         #   ACTION: Not handleable as the content is encrypted.
         # multipart/related (rfc 1872, 2112, 2387):
-        #   The Multipart/Related content-type addresses the MIME representation         
-        #   of compound objects.
+        #   The Multipart/Related content-type addresses the MIME
+        #   representation of compound objects.
         #   ACTION: Default. If we are lucky there is a text/plain.
         #   TODO: One should use the start part and look for an Alternative
         #   that is text/plain.
@@ -584,7 +604,15 @@ not find a text/plain part to use.
         else:
             content = self.get_part_data_decoded(message) 
  
-        summary, content = parseContent(content)
+        # figure how much we should muck around with the email body
+        keep_citations = getattr(self.instance, 'EMAIL_KEEP_QUOTED_TEXT',
+            'no') == 'yes'
+        keep_body = getattr(self.instance, 'EMAIL_LEAVE_BODY_UNCHANGED',
+            'no') == 'yes'
+
+        # parse the body of the message, stripping out bits as appropriate
+        summary, content = parseContent(content, keep_citations, 
+            keep_body)
 
         # 
         # handle the attachments
@@ -622,25 +650,13 @@ not find a text/plain part to use.
                             current_status == resolved_id):
                         props['status'] = chatting_id
 
-            # add nosy in arguments to issue's nosy list
-            if not props.has_key('nosy'): props['nosy'] = []
-            n = {}
+            # update the nosy list
+            current = {}
             for nid in cl.get(nodeid, 'nosy'):
-                n[nid] = 1
-            for value in props['nosy']:
-                if self.db.hasnode('user', value):
-                    nid = value
-                else: 
-                    continue
-                if n.has_key(nid): continue
-                n[nid] = 1
-            props['nosy'] = n.keys()
-            # add assignedto to the nosy list
-            if props.has_key('assignedto'):
-                assignedto = props['assignedto']
-                if assignedto not in props['nosy']:
-                    props['nosy'].append(assignedto)
+                current[nid] = 1
+            self.updateNosy(cl, props, author, recipients, current)
 
+            # create the message
             message_id = self.db.msg.create(author=author,
                 recipients=recipients, date=date.Date('.'), summary=summary,
                 content=content, files=files, messageid=messageid,
@@ -695,30 +711,7 @@ There was a problem with the message you sent:
             props['messages'] = [message_id]
 
             # set up (clean) the nosy list
-            nosy = props.get('nosy', [])
-            n = {}
-            for value in nosy:
-                nid = value
-                if n.has_key(nid): continue
-                n[nid] = 1
-            props['nosy'] = n.keys()
-            # add on the recipients of the message
-            for recipient in recipients:
-                if not n.has_key(recipient):
-                    props['nosy'].append(recipient)
-                    n[recipient] = 1
-
-            # add the author to the nosy list
-            if not n.has_key(author):
-                props['nosy'].append(author)
-                n[author] = 1
-
-            # add assignedto to the nosy list
-            if properties.has_key('assignedto') and props.has_key('assignedto'):
-                assignedto = props['assignedto']
-                if not n.has_key(assignedto):
-                    props['nosy'].append(assignedto)
-                    n[assignedto] = 1
+            self.updateNosy(cl, props, author, recipients)
 
             # and attempt to create the new node
             try:
@@ -732,8 +725,60 @@ There was a problem with the message you sent:
             # commit the new node(s) to the DB
             self.db.commit()
 
-def parseContent(content, blank_line=re.compile(r'[\r\n]+\s*[\r\n]+'),
-        eol=re.compile(r'[\r\n]+'), signature=re.compile(r'^[>|\s]*[-_]+\s*$')):
+        return nodeid
+
+    def updateNosy(self, cl, props, author, recipients, current=None):
+        '''Determine what the nosy list should be given:
+
+            props:      properties specified on the subject line of the message
+            author:     the sender of the message
+            recipients: the recipients (to, cc) of the message
+            current:    if the issue already exists, this is the current nosy
+                        list, as a dictionary.
+        '''
+        if current is None:
+            current = {}
+            ok = ('new', 'yes')
+        else:
+            ok = ('yes',)
+
+        # add nosy in arguments to issue's nosy list
+        nosy = props.get('nosy', [])
+        for value in nosy:
+            if not self.db.hasnode('user', value):
+                continue
+            if not current.has_key(value):
+                current[value] = 1
+
+        # add the author to the nosy list
+        if getattr(self.instance, 'ADD_AUTHOR_TO_NOSY', 'new') in ok:
+            if not current.has_key(author):
+                current[author] = 1
+
+        # add on the recipients of the message
+        if getattr(self.instance, 'ADD_RECIPIENTS_TO_NOSY', 'new') in ok:
+            for recipient in recipients:
+                if not current.has_key(recipient):
+                    current[recipient] = 1
+
+        # add assignedto(s) to the nosy list
+        if props.has_key('assignedto'):
+            propdef = cl.getprops()
+            if isinstance(propdef['assignedto'], hyperdb.Link):
+                assignedto_ids = [props['assignedto']]
+            elif isinstance(propdef['assignedto'], hyperdb.Multilink):
+                assignedto_ids = props['assignedto']
+            for assignedto_id in assignedto_ids:
+                if not current.has_key(assignedto_id):
+                    current[assignedto_id] = 1
+
+        props['nosy'] = current.keys()
+
+def parseContent(content, keep_citations, keep_body,
+        blank_line=re.compile(r'[\r\n]+\s*[\r\n]+'),
+        eol=re.compile(r'[\r\n]+'), 
+        signature=re.compile(r'^[>|\s]*[-_]+\s*$'),
+        original_message=re.compile(r'^[>|\s]*-----Original Message-----$')):
     ''' The message body is divided into sections by blank lines.
     Sections where the second and all subsequent lines begin with a ">" or "|"
     character are considered "quoting sections". The first line of the first
@@ -765,9 +810,9 @@ def parseContent(content, blank_line=re.compile(r'[\r\n]+\s*[\r\n]+'),
                 if line[0] not in '>|':
                     break
             else:
-                # TODO: people who want to keep quoted bits will want the
-                # next line...
-                # l.append(section)
+                # we keep quoted bits if specified in the config
+                if keep_citations:
+                    l.append(section)
                 continue
             # keep this section - it has reponse stuff in it
             if not summary:
@@ -781,15 +826,61 @@ def parseContent(content, blank_line=re.compile(r'[\r\n]+\s*[\r\n]+'),
             # if we don't have our summary yet use the first line of this
             # section
             summary = lines[0]
-        elif signature.match(lines[0]):
+        elif signature.match(lines[0]) and 2 <= len(lines) <= 10:
+            # lose any signature
+            break
+        elif original_message.match(lines[0]):
+            # ditch the stupid Outlook quoting of the entire original message
             break
 
         # and add the section to the output
         l.append(section)
-    return summary, '\n\n'.join(l)
+    # we only set content for those who want to delete cruft from the
+    # message body, otherwise the body is left untouched.
+    if not keep_body:
+        content = '\n\n'.join(l)
+    return summary, content
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.72  2002/05/22 01:24:51  richard
+# Added note to MIGRATION about new config vars. Also made us more resilient
+# for upgraders. Reinstated list header style (oops)
+#
+# Revision 1.71  2002/05/08 02:40:55  richard
+# grr
+#
+# Revision 1.70  2002/05/06 23:40:07  richard
+# hrm
+#
+# Revision 1.69  2002/05/06 23:37:21  richard
+# Tweaking the signature deletion from mail messages.
+# Added nuking of the "-----Original Message-----" crap from Outlook.
+#
+# Revision 1.68  2002/05/02 07:56:34  richard
+# . added option to automatically add the authors and recipients of messages
+#   to the nosy lists with the options ADD_AUTHOR_TO_NOSY (default 'new') and
+#   ADD_RECIPIENTS_TO_NOSY (default 'new'). These settings emulate the current
+#   behaviour. Setting them to 'yes' will add the author/recipients to the nosy
+#   on messages that create issues and followup messages.
+# . added missing documentation for a few of the config option values
+#
+# Revision 1.67  2002/04/23 15:46:49  rochecompaan
+#  . stripping of the email message body can now be controlled through
+#    the config variables EMAIL_KEEP_QUOTED_TEST and
+#    EMAIL_LEAVE_BODY_UNCHANGED.
+#
+# Revision 1.66  2002/03/14 23:59:24  richard
+#  . #517734 ] web header customisation is obscure
+#
+# Revision 1.65  2002/02/15 00:13:38  richard
+#  . #503204 ] mailgw needs a default class
+#     - partially done - the setting of additional properties can wait for a
+#       better configuration system.
+#
+# Revision 1.64  2002/02/14 23:46:02  richard
+# . #516883 ] mail interface + ANONYMOUS_REGISTER
+#
 # Revision 1.63  2002/02/12 08:08:55  grubert
 #  . Clean up mail handling, multipart handling.
 #