import os, sys import re, getopt, glob import urllib, urllib2 import mimetypes settings = {} supported_encodings = ['ms932', 'utf-8', 'euc-jp', 'iso-2022-jp'] class Callable: def __init__(self, anycallable): self.__call__ = anycallable class MultipartPostHandler(urllib2.BaseHandler): handler_order = urllib2.HTTPHandler.handler_order - 10 def http_request(self, request): data = request.get_data() if data is not None and type(data) != str: v_files = [] v_vars = [] try: for(key, value) in data.items(): if type(value) == file: v_files.append((key, value)) else: v_vars.append((key, value)) except TypeError: systype, value, traceback = sys.exc_info() raise TypeError, "not a valid non-string sequence or mapping object", traceback if len(v_files) == 0: data = urllib.urlencode(v_vars, 1) else: boundary, data = self.multipart_encode(v_vars, v_files) contenttype = 'multipart/form-data; boundary=%s' % boundary if request.has_header('Content-type'): pass request.add_unredirected_header('Content-type', contenttype) request.add_data(data) return request def multipart_encode(vars, files, boundary = None, buffer = None): if boundary is None: boundary = mimetools.choose_boundary() if buffer is None: buffer = '' for(key, value) in vars: buffer += '--%s\r\n' % boundary buffer += 'Content-Disposition: form-data; name="%s"' % key buffer += '\r\n\r\n' + value + '\r\n' for(key, fd) in files: file_size = os.fstat(fd.fileno())[stat.ST_SIZE] filename = fd.name.split('/')[-1] contenttype = mimetypes.guess_type(filename)[0] or 'application/octet-stream' buffer += '--%s\r\n' % boundary buffer += 'Content-Disposition: form-data; name="%s"; filename="%s"\r\n' % (key, filename) buffer += 'Content-Type: %s\r\n' % contenttype fd.seek(0) buffer += '\r\n' + fd.read() + '\r\n' buffer += '--%s--\r\n\r\n' % boundary return boundary, buffer multipart_encode = Callable(multipart_encode) https_request = http_request def post_object(object, type=None): fields = { 'email': settings['usermail'], 'password': settings['password'], 'private': settings['private'], 'type': 'regular' } types = { 'audio': 'audio', 'video': 'video', 'application': None, } if type: fields['type'] = type['type'] fields[type['key']] = object else: mime = mimetypes.guess_type(object)[0] if not mime: raise RuntimeError('unknown content type of "%s"' % object) reg = re.compile('^([^/]+)/') m = reg.search(mime) if m.group(1) == 'text': fields['type'] = 'quote' fields['quote'] = open(object, 'rb').read() for encoding in supported_encodings: try: fields['quote'] = fields['quote'].decode(encoding).encode('utf-8') break except UnicodeError: continue elif m.group(1) == 'image': fields['type'] = 'photo' fields['data'] = open(object, 'rb').read() else: fields['type'] = types[m.group(1)] fields['data'] = open(object, 'rb').read() if not fields['type']: raise RuntimeError('unknown content type of "%s"' % object) u = None try: opener = urllib2.build_opener(MultipartPostHandler) u = opener.open('http://www.tumblr.com/api/write', fields) u.read() except urllib2.HTTPError, e: if e.code != 201: raise e finally: if u: u.close() return fields['type'], object def load_config(): file = None try: file = open(os.path.expanduser('~/.tumblr_post'), 'r') reg = re.compile('^([^=]+)=(\S*)$') while True: line = file.readline() if not line: break if line == '\n': continue m = reg.search(line) settings[m.group(1)] = m.group(2) finally: if (file): file.close() def main(argv): try: opts, args = getopt.gnu_getopt(sys.argv[1:], "rs") except getopt.GetoptError: print 'usage: %s [files ...]' % argv[0] return -1 load_config() for arg in args: if re.compile('^http[s]*://').match(arg): type, object = post_object(arg, {'type': 'link', 'key': 'url'}) print 'posted "%s" as %s' % (object, type) else: for file in glob.glob(arg): type, object = post_object(file) print 'posted "%s" as %s' % (object, type) return 0 if __name__ == '__main__': sys.exit(main(sys.argv))