#!/usr/local/bin/python # see xxx for further enlightenment import email import email.Errors import email.Utils import email.Header import email.Message from email.Header import decode_header import rfc822 import mailbox import mimetypes import smtplib import xmlrpclib import base64 from ConfigParser import ConfigParser import os.path, os, sys, stat, time class ReReadingConfigParser(ConfigParser): def ReReadIfChanged(self): mtime = os.stat(self.config_name)[stat.ST_MTIME] # file has to be written for more than 2 seconds to # avoid reading a file while it is still written. if mtime > self.config_mtime and mtime > time.time() + 2: self.read(self.config_name) def read(self, fpname): # try to find out last modified date of file self.config_mtime = os.stat(fpname[0])[stat.ST_MTIME] self.config_name = fpname[0] return ConfigParser.read(self, fpname) def readfp(self, fp, filename=None): if filename is None: filename = fp.name ConfigParser.read(self, filename) def sections(self): self.ReReadIfChanged() return ConfigParser.sections(self) def options(self, section): self.ReReadIfChanged() return ConfigParser.options(self, section) def get(self, section, option, raw=0, vars=None): self.ReReadIfChanged() return ConfigParser.get(self, section, option, raw, vars) class MovingMaildir(mailbox.Maildir): """a variant of maildir which only processes new messages and moves messages already read to to cur.""" def __init__(self, dirname, factory=rfc822.Message): self.dirname = dirname self.factory = factory # check for new mail newdir = os.path.join(self.dirname, 'new') boxes = [os.path.join(newdir, f) for f in os.listdir(newdir) if f[0] != '.'] self.boxes = boxes # from http://c0re.23.nu/c0de/misc/python-maildir2.patch def next(self): if not self.boxes: return None fn = self.boxes[0] del self.boxes[0] fp = open(fn) # if the message is considered new, mark it as seen (head, tail) = os.path.split(fn) if(head[-3:] == 'new'): os.rename(fn, os.path.join(head[:-3], 'cur', tail + ':2,S')) return self.factory(fp) def getConfig(mailfrom, mailto): "get data from our config file" global cfg user_blog = "%s>%s" % (mailfrom, mailto) if not cfg.has_section(user_blog): print "ERROR: no configuration for %r" % user_blog return None ret = {'blogID': cfg.get(user_blog, 'weblog'), 'username': cfg.get(user_blog, 'username'), 'password': cfg.get(user_blog, 'password'), 'storybase': cfg.get(user_blog, 'storybase'), 'sendreply': cfg.getboolean(user_blog, 'sendreply'), 'server': cfg.get(user_blog, 'server'),} return ret def postImage(blog, data, typ, filename = None, description = ''): s = {'bits': base64.encodestring(data), 'type': typ, 'name': filename, 'description': description} srv = xmlrpclib.Server(blog['server']) ret = srv.metaWeblog.newMediaObject(blog['blogID'], blog['username'], blog['password'], s) return ret def postStory(blog, description, title = ''): s = {'title': title, 'description': description} publish = 1 srv = xmlrpclib.Server(blog['server']) ret = srv.metaWeblog.newPost(blog['blogID'], blog['username'], blog['password'], s, publish) if 'storybase' in blog: ret = blog['storybase'] + ret return ret def msgfactory(fp): try: return email.message_from_file(fp) except email.Errors.MessageParseError: # Don't return None since that will # stop the mailbox iterator return '' def processTextPart(part): charset = part.get_content_charset() if not charset: charset = 'iso-8859-1' return unicode(part.get_payload(decode=True), charset, 'replace') def decodeHeader(hdr): title = [] for decoded_string, charset in decode_header(hdr): if not charset: charset = 'iso-8859-1' title.append(unicode(decoded_string, charset, 'replace')) return u''.join(title).encode('iso-8859-1') def postMail(msg, mailfrom, mailto, blog): imgMacro = None text = [] for part in msg.walk(): # multipart/* are just containers if part.get_main_type() == 'multipart': continue if part.get_main_type() == 'text': text.append( processTextPart(part)) if part.get_main_type() == 'image': imgMacro = postImage(blog, part.get_payload(decode=1), part.get_type(), part.get_filename())['macro'] print "image posted" if imgMacro: text.append(unicode(imgMacro)) text = u'\n'.join(text).encode('iso-8859-1') title = decodeHeader(msg['subject']) return (postStory(blog, text, title), text, title) def waitUntilMaidirChanged(): last_dir = os.path.join(os.path.expanduser(cfg.get("Main", 'maildir')), 'new') last_mtime = os.stat(last_dir)[stat.ST_MTIME] while 1: if last_dir != os.path.join(os.path.expanduser(cfg.get("Main", 'maildir')), 'new'): return if os.stat(os.path.join(os.path.expanduser(cfg.get("Main", 'maildir')), 'new'))[stat.ST_MTIME] > last_mtime: return time.sleep(0.92) def sendReply(to, url, text, title): msg = email.Message.Message() msg['Subject'] = email.Header.Header("Posted: %s" % (title), 'iso-8859-1') msg['To'] = to msg['From'] = email.Utils.formataddr((str(email.Header.Header(cfg.get("Main", 'mailfromname'), 'iso-8859-1')), cfg.get("Main", 'mailfrom'))) rcpt = email.Utils.parseaddr(to)[1] msg.set_payload("posted to %s\n\n%s\n%s" % (url, title, text) ,'iso-8859-1') msg.set_type('text/plain') s = smtplib.SMTP() s.connect() s.sendmail(cfg.get("Main", 'mailfrom'), [to], msg.as_string()) s.close() def processMaildir(): m = MovingMaildir(os.path.expanduser(cfg.get("Main", 'maildir')), msgfactory) msg = m.next() while(msg): text = [] mailfrom = email.Utils.parseaddr(msg['from'])[1] mailto = email.Utils.parseaddr(msg['to'])[1] blog = getConfig(mailfrom, mailto) if blog: url, text, title = postMail(msg, mailfrom, mailto, blog) print email.Utils.parseaddr(msg['to'])[0], email.Utils.parseaddr(msg['to'])[1], print blog['blogID'], title if blog['sendreply']: sendReply(msg['from'], url, text, title) msg = m.next() cfg = ReReadingConfigParser() cfg.read([os.path.expanduser('~/moblog.ini')]) while 1: processMaildir() waitUntilMaidirChanged() print "new messages"