mail filter in python?

Bill Janssen janssen at parc.xerox.com
Mon Feb 7 20:47:01 EST 2000


I use a Python filter heavily with MH.  I first run regular MH "inc
+inbox" to pick up the mail from the spool file, then read a sorting
routine from ~/.mailfilter.py, which is called on each message, with
the message itself as an object parameter.  The object type of the message
is a subclass of mhlib.Message, which adds the methods

  matches(NAMES, HEADERNAMES) -- see if any of the specified NAMES occur
   in any of the headers with the specified HEADERNAMES

  addr_matches(ADDRESS, HEADERNAME) -- returns True if ADDRESS occurs in
   any of the instances of the header with HEADERNAME

  sentto (NAMES) -- returns True if the message was sent to any of the
   specified NAMES (which are just strings).  It looks at "To", "Cc",
   "Resent-To", and "Apparently-TO".

  copy (FOLDERNAME, UNSEEN=1) -- copies the message into the folder
   specified with FOLDERNAME.  Adds to the `unseen' sequence if UNSEEN
   is True.

  refile (FOLDERNAME, UNSEEN=1) -- similar to `copy', but deletes the
   message from the original folder.

  resend (TO) -- resends the message to the list of addresses in the TO
   parameter

  discard () -- deletes the message from its folder

The filter itself looks like

------------------------------------------------------------
# This file contains the rule which sorts my incoming mail when invoked by the "mhfile" command
#
# This file must define the procedure "mailfilterfn()".  It is eval'ed by the mhfile
# module upon load of mhfile.py.  It can be re'evaled by calling mhfile.read_filter_fn().

import mhfile, rfc822

def mailfilterfn (msg):

	if (msg.matches("<janssen>", "from")):
		msg.refile("overhead/outgoing")
		return

	...

	if (msg.sentto("python-list at cwi.nl", "python-list at python.org") or
	      msg.matches("comp.lang.python", "newsgroups")):
		msg.refile("ext/python")

	elif (msg.sentto("db-sig at python.org")):
		msg.refile("ext/python/db-sig")

	...

	##################################################
	##
	## Put any leftover messages in "mail"
	##

	else:
	    if not filed_in_my_direct:
		msg.refile("mail")
	    else:
		msg.discard()

	return
------------------------------------------------------------

and so forth.  Here's the code snippet from mhfile.py:

------------------------------------------------------------
	if (not read_filter_fn_if_necessary()):
	    sys.stderr.write("Can't read mail filter file.\n");
	    return
	incoming = jmhlib.Folder(mhlib.MH(), 'inbox')
	for msg in incoming.listmessages():
	    if verbose:
		print 'processing message', msg
	    try:
		m = jmhlib.Message(incoming, msg)
	    except:
		if verbose:
		    print "couldn't access msg " + str(msg)
		    traceback.print_exc()
	    mailfilterfn (m)
	    m.fp.close()
------------------------------------------------------------

I'll leave the implementation of read_filter_fn_if_necessary as an
exercise for the reader :-; it's kind of fun!

Here's the contents of jmhlib.py:

------------------------------------------------------------
# subclass of mhlib.Message for adding common operations

import sys, os, mhlib, types, string, rfc822, time, pwd, re, shutil, traceback
from mhlib import Folder

def add_msg_to_unseen (folder, msgnum):
    d = folder.getsequences()
    unseen = ((d.has_key("unseen") and d["unseen"]) or [])
    unseen.append(msgnum)
    d["unseen"] = unseen
    folder.putsequences(d)

class Message(mhlib.Message):

    # Check to see if any of the specified 'names' are in any of the
    # specified headers
    def matches (self, names, headers):
        if type(names) == types.StringType:
            names = ( names , )
        if type(headers) == types.StringType:
            headers = ( headers , )
        for header in headers:
            headerval = self.getheader(header)
            if not headerval:
                continue
            for name in names:
                testname = string.lower(name)
                testheader = string.lower(headerval)
                # print "looking for <%s> in <%s>" % (testname, testheader)
                if (string.find(testheader, testname) >= 0):
                    # print " *** found it"
                    return 1
        return 0

    def addr_matches (self, mail_addr, header_name):
	# returns 1 if "mail_addr" is in any of the addresses in the headers with the name "header_name"
	real_header_name = string.lower(header_name)
	real_mail_addr = string.lower(mail_addr)
	if not hasattr(self, "_addrs"):
	    self._addrs = {}
	if not self._addrs.has_key(real_header_name):
	    self._addrs[real_header_name] = self.getaddrlist(real_header_name)
	test_values = self._addrs[real_header_name]
	for value in test_values:
	    # each value is a pair of (comment, mail_addr)
	    if ((string.find(string.lower(value[0]), real_mail_addr) >= 0) or
		(string.find(string.lower(value[1]), real_mail_addr) >= 0)):
		return 1
	return 0

    # was the message sent to any of the specified names?
    def sentto (self, *names):
	for header_name in ("to", "resent-to", "apparently-to", "cc"):
	    for name in names:
		if self.addr_matches (name, header_name):
		    return 1
	return 0

    # put a copy of the message into another folder
    def copy (self, foldername, unseen=1):
        newfolder = Folder(self.folder.mh, foldername)
	ton = newfolder.getlast() + 1
        # print "copying %s into %s/%s" % (self.number, foldername, ton)
        self.folder.copymessage(self.number, newfolder, ton)
	if unseen:
	    add_msg_to_unseen (newfolder, ton)

    # put the message in a different folder
    def refile (self, foldername, unseen=1):
        # print "putting %s into %s" % (self.number, foldername)
	self.copy(foldername, unseen)
	self.discard()

    # resend the message to the specified names
    def resend(self, *to):
        os.system('/usr/lib/sendmail %s <%s' % (string.join(to), self.folder.getmessagefilename(self.number)))

    # discard message
    def discard(self):
        self.folder.removemessages([self.number])
------------------------------------------------------------

Have fun!

Bill






More information about the Python-list mailing list