This article was originally published by Python Magazine in October of 2007.

Working with IMAP and iCalendar

Listing2.py

#!/usr/bin/env python
# mailbox2ics.py

"""Convert the contents of an imap mailbox to an ICS file.

This program scans an IMAP mailbox, reads in any messages with ICS
files attached, and merges them into a single ICS file as output.
"""

# Import system modules
import imaplib
import email
import getpass
import optparse
import sys

# Import Local modules
from icalendar import Calendar, Event

# Module

def main():
    # Set up our options
    option_parser = optparse.OptionParser(
        usage='usage: %prog [options] hostname username mailbox [mailbox...]'
        )
    option_parser.add_option('-p', '--password', dest='password',
                             default='',
                             help='Password for username',
                             )
    option_parser.add_option('--port', dest='port',
                             help='Port for IMAP server',
                             type="int",
                             )
    option_parser.add_option('-v', '--verbose', 
                             dest="verbose", 
                             action="store_true", 
                             default=True,
                             help='Show progress',
                             )
    option_parser.add_option('-q', '--quiet', 
                             dest="verbose", 
                             action="store_false", 
                             help='Do not show progress',
                             )
    option_parser.add_option('-o', '--output', dest="output",
                             help="Output file",
                             default=None,
                             )

    (options, args) = option_parser.parse_args()
    if len(args) < 3:
        option_parser.print_help()
        print >>sys.stderr, '\nERROR: Please specify a username, hostname, and mailbox.'
        return 1
    hostname = args[0]
    username = args[1]
    mailboxes = args[2:]

    # Make sure we have the credentials to login to the IMAP server.
    password = options.password or getpass.getpass(stream=sys.stderr)

    # Initialize a calendar to hold the merged data
    merged_calendar = Calendar()
    merged_calendar.add('prodid', '-//mailbox2ics//doughellmann.com//')
    merged_calendar.add('calscale', 'GREGORIAN')

    if options.verbose:
        print >>sys.stderr, 'Logging in to "%s" as %s' % (hostname, username)

    # Connect to the mail server
    if options.port is not None:
        mail_server = imaplib.IMAP4_SSL(hostname, options.port)
    else:
        mail_server = imaplib.IMAP4_SSL(hostname)
    (typ, [login_response]) = mail_server.login(username, password)
    try:
        # Process the mailboxes
        for mailbox in mailboxes:
            if options.verbose: print >>sys.stderr, 'Scanning %s ...' % mailbox
            (typ, [num_messages]) = mail_server.select(mailbox)
            if typ == 'NO':
                raise RuntimeError('Could not find mailbox %s: %s' % 
                                   (mailbox, num_messages))
            num_messages = int(num_messages)
            if not num_messages:
                if options.verbose: print >>sys.stderr, '  empty'
                continue

            # Find all messages
            (typ, [message_ids]) = mail_server.search(None, 'ALL')
            for num in message_ids.split():

                # Get a Message object
                typ, message_parts = mail_server.fetch(num, '(RFC822)')
                msg = email.message_from_string(message_parts[0][1])

                # Look for calendar attachments
                for part in msg.walk():
                    if part.get_content_type() == 'text/calendar':
                        # Parse the calendar attachment
                        ics_text = part.get_payload(decode=1)
                        importing = Calendar.from_string(ics_text)

                        # Add events from the calendar to our merge calendar
                        for event in importing.subcomponents:
                            if event.name != 'VEVENT':
                                continue
                            if options.verbose: 
                                print >>sys.stderr, 'Found: %s' % event['SUMMARY']
                            merged_calendar.add_component(event)
    finally:
        # Disconnect from the IMAP server
        if mail_server.state != 'AUTH':
            mail_server.close()
        mail_server.logout()

    # Dump the merged calendar to our output destination
    if options.output:
        output = open(options.output, 'wt')
        try:
            output.write(str(merged_calendar))
        finally:
            output.close()
    else:
        print str(merged_calendar)
    return 0

if __name__ == '__main__':
    try:
        exit_code = main()
    except Exception, err:
        print >>sys.stderr, 'ERROR: %s' % str(err)
        exit_code = 1
    sys.exit(exit_code)

Original Format