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:
- my vaguely okay wrapper for command line python utilities, concealed in the Main() class.
- vobject is pretty good for handling vCard and iCal objects. I’ve used it a few times.
- if you ever want to open a file using the correct handler under GNOME/KDE, xdg-open is your friend.
- my ingenious solution for not knowing when the contact handler (in my case, Kontact) will actually pick up the vCard, which is stored in a temporary file that I delete at the end of my program — just wait five seconds, and then delete it. How hacky is that?
- that python’s subprocess module badly needs more helper utilities, IMHO. That subprocess.Popen stuff is just Python’s new way of saying “run a shell command”, and it should be clearer than that.
#!/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)

August 18th, 2008 at 5:31 am
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 !
August 18th, 2008 at 8:03 pm
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!
August 19th, 2008 at 4:39 pm
Oh, there’s a slight bug there, too
if ',' in name: (family, givenname) = name.split(',')should be
if ',' in name: (givenname, family) = name.split(',')