skip to main bit
a man slumped on his desk, from 'The Sleep of Reason Produces
      Monsters'

Oblomovka

Currently:

inbox seventy-two thousand; email2vcard

Typically my spare time today was spent in the folllowing proportions: 2 hours allotted to emptying my inbox; 3 minutes spent actually emptying my inbox; 1 hour, 57 minutes spent writing a program to theoretically “help” me empty my inbox.

Spreading the procrastination around, here’s that code. It’s for the millions of people who, like me, use good old command line mutt and a graphical contact manager on Unix (although I think it may have more uses than that). It takes a standard email, pulls out the name and email of the person the mail is from, generates a vCard, and then attempts to open that vCard in your nearby contact manager. It uses vobject, hewn from the glowing pits of Chandler, which you can install with easy_install vobject or apt-get install python-vobject. Hours of entertainment.

Things you might be able to glean from this:

#!/usr/bin/env python
##
# email2vcf.py
###
"""email2vcf.py

"""

__version__ = "0.1"
__author__ = "Danny O'Brien "
__license__ = "GPL v3"

import vobject
import email
from email.Utils import parseaddr
import tempfile, os, subprocess, time

class VcardableMessage(email.Message.Message):
    def toVcard(self):
        r"""
        >>> from  StringIO import StringIO as st
        >>> em = "To: me \n\nThis is my phone numner: +1 555 134 5678\nThanks!\n\nd."
        >>> p = email.Parser.Parser(VcardableMessage)
        >>> m = p.parse(st(em))
        >>> v = m.toVcard()
        >>> print v.n
        
        >>> print v.fn
        
        >>> print v.email
        
        >>> print v.note.value
        From email message-id: Unknown
        Dated: Unknown
        Subject: a test
        """
        v = vobject.vCard()
        (name, email)  = parseaddr(self['From'])
        family = None
        givenname = None
        if ',' in name:
            (family, givenname) = name.split(',')
        if ' ' in name:
            (family, givenname) = name.rsplit(' ', 1)
        if not family:
            givenname = name
        v.add('fn').value  = name
        v.add('n').value = vobject.vcard.Name(family = family, given = givenname)
        v.add('email').value = email
        mid = self.get('message-id', 'Unknown')
        dt = self.get('date', 'Unknown')
        sub = self.get('subject','Unknown')
        v.add('note').value = 'From email message-id: %s\nDated: %s\nSubject: %s' % (mid, dt, sub)
        return v

def main(args):
    """ Convert mail to vcard, then kick up systems' vcard handler"""
    if len(args) == 0:
        st = sys.stdin
    if len(args) == 1:
        st = file(args[0],'r')
    p = email.Parser.Parser(VcardableMessage)
    m = p.parse(st)
    v = m.toVcard()
    f = tempfile.NamedTemporaryFile(prefix="email2vcf", suffix=".vcf")
    f.write(v.serialize())
    f.flush()
    p= subprocess.Popen("xdg-open" + " " + f.name, shell=True)
    sts = os.waitpid(p.pid, 0)
    time.sleep(5) # give app long enough to import the data
    f.close()

import sys, getopt
class Main():
    """ Encapsulates option handling. Subclass to add new options,
        add 'handle_x' method for an -x option,
        add 'handle_xlong' method for an --xlong option
        help (-h, --help) should be automatically created from module
        docstring and handler docstrings.
        test (-t, --test) will run all docstring and unittests it finds
        """
    class Usage(Exception):
        def __init__(self, msg):
            self.msg = msg
    def __init__(self):
        handlers  = [i[7:] for i in dir(self) if i.startswith('handle_') ]
        self.shortopts = ''.join([i for i in handlers if len(i) == 1])
        self.longopts = [i for i in handlers if (len(i) > 1)]
    def handler(self,option):
        i = 'handle_%s' % option.lstrip('-')
        if hasattr(self, i):
           return getattr(self, i)
    def default_main(self, args):
        print sys.argv[0]," called with ", args
    def handle_help(self, v):
        """ Shows this message """
        print sys.modules.get(__name__).__doc__
        descriptions = {}
        for i in list(self.shortopts) + self.longopts:
            d=self.handler(i).__doc__
            if d in descriptions:
               descriptions[d].append(i)
            else:
               descriptions[d] = [i]
        for d, o in descriptions.iteritems():
            for i in o:
                if len(i) == 1:
                    print '-%s' % i,
                else:
                    print '--%s' % i,
            print
            print d
        sys.exit(0)
    handle_h=handle_help

    def handle_test(self, v):
        """ Runs test suite for file """
        import doctest
        import unittest
        suite = unittest.defaultTestLoader.loadTestsFromModule(sys.modules.get(__name__))
        suite.addTest(doctest.DocTestSuite())
        runner = unittest.TextTestRunner()
        runner.run(suite)
        sys.exit(0)
    handle_t=handle_test

    def run(self, main= None, argv=None):
        """ Execute main function, having stripped out options and called the
        responsible handler functions within the class. Main defaults to
        listing the remaining arguments.
        """
        if not callable(main):
            main = self.default_main
        if argv is None:
            argv = sys.argv
        try:
            try:
                opts, args = getopt.getopt(argv[1:], self.shortopts, self.longopts)
            except getopt.error, msg:
                raise self.Usage(msg)
            for o, a in opts:
                (self.handler(o))(a)
            return main(args)
        except self.Usage, err:
            print >>sys.stderr, err.msg
            self.handle_help(None)
            return 2

if __name__ == "__main__":
    sys.exit(Main().run(main) or 0)

3 Responses to “inbox seventy-two thousand; email2vcard”

  1. M-.-n Says:

    nice lifehack.. now you have an empty mailbox and a contact manager full of junk ? :)

    I found the best way to clean my inbox (if I ever feel like it) is to order it first by subject and then by sender. You’d be amazed how everything is linked together and bulk of mails stand out ready for deletion.Wurkz !

  2. Danny O'Brien Says:

    sadly, i don’t think i even used it on the mail that I wanted to use it on…

    yes, that is a good tip! i’ve heard that from Cory too!

  3. Danny O'Brien Says:

    Oh, there’s a slight bug there, too

    if ',' in name:
                (family, givenname) = name.split(',')
    

    should be

    if ',' in name:
                (givenname, family) = name.split(',')
    

                                                                                                                                                                                                                                                                                                           

petit disclaimer:
My employer has enough opinions of its own, without having to have mine too.