#! /usr/bin/env python """Expire old messages in an IMAP folder. Just specify the number of days to keep, and this simple script connects to, or runs directly, the IMAP4 server, and deletes (and optionally expunges) anything older. (Messages that have their "flag" set are not deleted.) Very little error checking is done; so the script will probably just crash with ugly error messages if the IMAP server runs into any problems. %s If no password is specified or found in the config file you will be prompted to supply a password (unless using a local imap server). You must at least specify a number of days to keep messages on the command line for this script to run. The (optional) config file needs to have "[DEFAULT]" string at the top. It can have any or none of the following values: username: password: domain: port: sslmode: localimap: Additionally the config file may have sections with different settings for a imap server domain name. Create a "[domain.name]" line, and then specify any of the values as described above in it. Please note (again!) that it can be dangerous to store your password in files. Make sure you know what you are doing, if you do this. Tim Middleton $Id: expimap.py,v 1.5 2005/04/09 22:34:05 x Exp $ """ # These defaults can be set in an config file or given on the command line. folder = "INBOX" # mailbox (may require folder like 'mail/myfolder') username = None # login user name password = None # password to authenticate domain = "localhost" # imap server localimap= False # imapd program name (instead of connecting via net) port = None # imap port (default will use 143, or 993 if SSL) dayskeep = -1 # number of days of messages to keep expunge = False # expunge after flagging as deleted? testmode = False # mailbox not altered if set sslmode = False # use SSL in connecting to imap #### Nothing of interest below here.... import imaplib, time, sys, os, string, __main__ from ConfigParser import SafeConfigParser as ConfigParser from optparse import OptionParser __constants__ = ('folder', 'username', 'password', 'dayskeep', 'domain', 'port', 'expunge', 'testmode', 'sslmode', 'localimap') __constant_bool__ = ('expunge', 'testmode', 'sslmode') __constant_int__ = ('dayskeep', 'port') #### parse command line arguments: parsedOpts = OptionParser() parsedOpts.add_option( '-c', '--cfgfile', dest='cfgfile', default=os.path.expanduser('~/.expimaprc') ) parsedOpts.add_option( '-d', '--domain', dest='domain' ) parsedOpts.add_option( '-e', '--expunge', action="store_true", dest='expunge' ) parsedOpts.add_option( '-f', '--folder', dest='folder', default='INBOX' ) parsedOpts.add_option( '-l', '--localimap', dest='localimap' ) parsedOpts.add_option( '-p', '--port', dest='port', type="int" ) parsedOpts.add_option( '-s', '--ssl', '--sslmode', action="store_true", dest='sslmode' ) parsedOpts.add_option( '-t', '--testmode', action="store_true", dest='testmode' ) parsedOpts.add_option( '-u', '--username', dest='username' ) parsedOpts.add_option( '-w', '--password', dest='password' ) parsedOpts.usage = "usage: %prog [options] days" opts, args = parsedOpts.parse_args() print if args: try: dayskeep = int(args[0]) except: pass if dayskeep<0: print globals()['__doc__'] % parsedOpts.format_help() sys.exit() try: cfgfile = ConfigParser() cfgfile.read(opts.cfgfile) except: cfgfile = cfgopts = None print "Warning: config file %s had errors and is being ignored." % opts.cfgfile print "-" * 75 print "%s: %s" % tuple(sys.exc_info()[:2]) print "-" * 75 # merge config file options if cfgfile: if opts.domain and cfgfile.has_section(opts.domain): section = opts.domain else: section = 'DEFAULT' for k,v in cfgfile.items(section): if k not in __constants__: continue if k in __constant_bool__: v = cfgfile.getboolean(section, k) if k in __constant_int__: v = cfgfile.getint(section, k) setattr(__main__, k, v) # merge in the command line arguments for k in __constants__: if hasattr(opts, k): if getattr(opts, k) is not None: setattr(__main__, k, getattr(opts, k)) if not port: if not sslmode: port = 143 else: port = 993 if not localimap: if not password: import getpass password = getpass.getpass() if not username: import getpass username = getpass.getuser() if not password: print "ERROR: We will need a password. Aboring" sys.exit(1) months = (None, "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec") ts = time.localtime(time.time() - (dayskeep * 86400)) date_text = "%s-%s-%s" % (ts[2], months[ts[1]], ts[0]) if testmode: print """\ testmode: True username: %(username)s password: domain: %(domain)s port: %(port)s folder: %(folder)s sslmode: %(sslmode)s expunge: %(expunge)s dayskeep: %(dayskeep)s localimap: %(localimap)s """ % globals() if localimap: # Thanks to Eliot Lear for adding the IMAP4_stream option print """calling %s...""" % localimap i = imaplib.IMAP4_stream(localimap) else: if sslmode: IMAP = imaplib.IMAP4_SSL else: IMAP = imaplib.IMAP4 print """Connected to %s:%s...""" % (domain, port) i = IMAP(domain, port) i.login(username, password) print """Logged in as '%s' successfully...""" % username r = i.select(folder) if r[0] <> 'OK': print """ERROR: Could not select mailbox '%s'.""" % folder sys.exit() msgcount = int(r[1][0]) print "%s message in '%s'..." % (msgcount, folder) print "Finding messages older than %s..." % date_text if testmode: print '(TEST MODE ACTIVE - mailbox will not be altered)' m = i.search(None, 'UNFLAGGED', 'UNDELETED', 'BEFORE', date_text) count = 0 for st in m[1]: if st: sts = string.split(st) count = count + len(sts) if not testmode: while sts: i.store(string.join(sts[:1000],','), '+FLAGS.SILENT', '\deleted') del sts[:1000] print "%s messages flagged for deletion..." % count if expunge and count: i.expunge() print "and expunged!" else: print "and that is all."