#!/home/okin/bin/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: <login name>
    password: <password>
    domain: <server host name>
    port: <port to use>
    sslmode: <yes/no>
    localimap: <path to local imap server program>

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 <x@Vex.Net>
$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

onlyread = False        # only move messages that have been read
copy_to = None          # folder to copy messages to, if any

#### 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', 'copy_to')
__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.add_option( '-r', '--only-read', 
                       action="store_true", dest='onlyread' )
parsedOpts.add_option( '--copy-to', dest='copy_to' )

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:   <not shown>
domain:     %(domain)s
port:       %(port)s
folder:     %(folder)s
sslmode:    %(sslmode)s
expunge:    %(expunge)s
dayskeep:   %(dayskeep)s
localimap:  %(localimap)s
only-read:  %(onlyread)s
copy-to:    %(copy_to)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)'

flags = [None, 'UNFLAGGED', 'UNDELETED', 'BEFORE', date_text]
if onlyread: flags += ['SEEN']
m = i.search(*flags)
count = 0
for st in m[1]:
    if st:
        sts = string.split(st)
        count = count + len(sts)
        if not testmode:
            while sts:
                msgnums = string.join(sts[:1000],',')
                if copy_to:
                    i.copy(msgnums, copy_to)
                i.store(msgnums, '+FLAGS.SILENT', '\deleted')
                del sts[:1000]
print "%s messages flagged for deletion..." % count
if copy_to:
    print "(and copied to %s)" % copy_to
if expunge and count:
    i.expunge()
    print "and expunged!"
else:
    print "and that is all."


