# mail2micropost.py - levanta mail y lo manda a twitter/identi.ca # Copyright (C) 2009 - Facundo Batista # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from __future__ import with_statement import base64 import urllib2 import sys import time import urllib import urlparse import logging import imaplib import email import re from email.header import decode_header import simplejson import infoauth # logging stuff _logger = logging.getLogger("main") _logger.setLevel(logging.DEBUG) ch = logging.FileHandler('micropost.log') ch.setLevel(logging.DEBUG) formatter = logging.Formatter("%(asctime)s - %(message)s") ch.setFormatter(formatter) _logger.addHandler(ch) logger = _logger.debug CHARACTER_LIMIT = 140 SERVICES = [ 'http://twitter.com/statuses/update.json', 'http://identi.ca/api/statuses/update.xml', ] AUTH_ORIGIN = 'facundo@taniquetil.com.ar' AUTH_RE = re.compile('.*<(.*@.*)>.*') MAILSERVER = "mail.taniquetil.com.ar" IMG_BASE_URL = "http://www.taniquetil.com.ar/facundo/mblogimgs/" IMG_BASE_DIR = "/var/www/facundo/mblogimgs/" def get_opener(url, username, password): handler = urllib2.HTTPBasicAuthHandler() (scheme, netloc, path, params, query, fragment) = urlparse.urlparse(url) handler.add_password('Twitter API', netloc, username, password) opener = urllib2.build_opener(handler) basic_auth = base64.encodestring('%s:%s' % (username, password))[:-1] opener.addheaders = [('Authorization', 'Basic %s' % basic_auth)] return opener def impact(url, message, username, password): '''Fetch a URL.''' opener = get_opener(url, username, password) encoded_post_data = urllib.urlencode(dict(status=message.encode("utf8"))) # Open and return the URL immediately if we're not going to cache url_data = opener.open(url, encoded_post_data).read() opener.close() # Always return the latest version return url_data def get_from_mail(usr, pwd): # abrimos la conexion y nos fijamos cuantos hay m = imaplib.IMAP4(MAILSERVER) m.login(usr, pwd) cant = m.select() if cant == 0: return # buscamos todos typ, data = m.search(None, 'ALL') if typ != "OK": raise ValueError("Recibimos %r al hacer el search" % typ) # traemos los mails info = [] for num in data[0].split(): typ, data = m.fetch(num, '(RFC822)') msg = email.message_from_string(data[0][1]) yield msg m.store(num, '+FLAGS', '\\Deleted') # expunge all deleted messages, and leave m.expunge() m.close() m.logout() def post_image(image, fname, bitly_usr, bitly_key): '''Saves an image to disk and gets a short url.''' # save image to disk fullname = time.strftime("%Y%m%d%H%M%S") + "-" + fname logger("Save image %s (len %d)", fullname, len(image)) with open(IMG_BASE_DIR + fullname, "w") as fh: fh.write(image) # get short url fullurl = IMG_BASE_URL + fullname bitly = "http://api.bit.ly/shorten?version=2.0.1&longUrl=%s&login=%s"\ "&apiKey=%s" % (fullurl, bitly_usr, bitly_key) try: u = urllib2.urlopen(bitly) resp = u.read() except Exception, e: logger("Problems opening %r (%s)", bitly, e) return "" j = simplejson.loads(resp) if j["errorCode"] != 0: logger("Bad error code in bit.ly response: %s", j) return "" try: shorturl = j["results"][fullurl]["shortUrl"] except Exception, e: logger("Can't get (%s) short url in response: %s", e, j) return "" logger('Short ulr ok: %r', shorturl) return shorturl def get_messages(usr, pwd, bitly_usr, bitly_key): for mail in get_from_mail(usr, pwd): # validate origin origen = mail.get("From") logger("Got message from %r", origen) m = AUTH_RE.match(origen) if not m or m.groups()[0] != AUTH_ORIGIN: continue # compose message message = "" subject = mail.get("Subject") if subject: header, encoding = decode_header(subject)[0] if not encoding: encoding = "ascii" message += header.decode(encoding).strip() + ": " imagen = None url = "" for part in mail.walk(): mime = part.get_content_type() payload = part.get_payload(decode=True) if mime == "text/plain": message += payload.decode("utf8").strip() elif mime.split("/")[0] == "image": imagen = payload fname = part.get_filename("") if imagen: url = post_image(imagen, fname, bitly_usr, bitly_key) yield message, url def generate_auth(): mb_usr = raw_input("Ingrese el usuario de microblogging: ") mb_pwd = raw_input("Ingrese la password del mismo: ") mail_usr = raw_input("Ingrese el usuario de correo: ") mail_pwd = raw_input("Ingrese la password del mismo: ") bly_usr = raw_input("Ingrese el usuario de bit.ly: ") bly_key = raw_input("Ingrese la api key correspondiente: ") data = (mb_usr, mb_pwd, mail_usr, mail_pwd, bly_usr, bly_key) infoauth.dump(data, "auth.pkl") def main(auth_file): logger("Starting") mb_usr, mb_pwd, mail_usr, mail_pwd, b_usr, b_key = infoauth.load(auth_file) logger("Info auth ok") for message, imgurl in get_messages(mail_usr, mail_pwd, b_usr, b_key): logger("Message! text %r imgurl %r", message, imgurl) if len(message) + len(imgurl) + 1 > CHARACTER_LIMIT: message = message[:CHARACTER_LIMIT - len(imgurl) - 1] logger("Message truncated! Now it's: %r", message) message += " " + imgurl for url in SERVICES: logger("Impact to %s", url) try: resp = impact(url, message, mb_usr, mb_pwd) except Exception, e: logger("Exception! %s", e) else: logger("Impact result: %s", resp) logger("All done!") def usage(): print "Usar: mail2micropost.py {--auth|--go auth.pkl}" print " --go will do the magic using the given auth file" print " --auth will ask for info and dump a auth.pkl file" sys.exit() if __name__ == "__main__": if len(sys.argv) not in (2, 3): usage() if sys.argv[1] == "--auth": generate_auth() elif sys.argv[1] == "--go": main(sys.argv[2]) else: usage()