#!/usr/bin/env python """Flickr uploader. Faried Nawaz wrote this in October 2005; he wasn't aware of any python libraries at the time. Updated 2008-11-28 to - fix a bug reported by Grzegorz Wilk - fix a few style warnings reported by pylint - use webbrowser instead of invoking firefox - use xml.dom.minidom instead of parsing XML manually. This code is in the public domain. """ import getopt import httplib import md5 import mimetypes import os import sys import time import urllib import urlparse import webbrowser from xml.dom.minidom import parseString RESTURL = 'http://www.flickr.com/services/rest/' TOKENFILE = os.environ['HOME'] + '/.flickr-upload-token' # replace the following two lines with your key/secret # if you don't have an api key, login to flickr and go to # http://www.flickr.com/services/api/key.gne # to get one for a 'desktop application'. APIKEY = '45584dff059489d06fdf471a5b7d9378' SHAREDSECRET = '46a396cf11c5df3c' # replace this with your application's name. USERAGENT = 'UploaderForToshok/2008-11-28' class Oops(Exception): """Generic Exception.""" def __init__(self, value): self.value = value def __str__(self): return self.value def usage(msg=None): """Tell the user how to use this app.""" progname = sys.argv[0] if msg: sys.stderr.write(msg + '\n') sys.stderr.write('''\ usage: %s [options] pic.jpg [pic2.png pic3.gif ...] Optional arguments: \t-t|--title="a title" \t-d|--description="a description" \t-T|--tags="tag1 tag2 ..." \t-p|--public=1 (0 for no, 1 for yes, defaults to yes) \t-F|--friend=0 (defaults to no) \t-A|--family=0 (defaults to no) ''' % progname) sys.exit(1) def getopts(): """Handle commandline arguments.""" optargs = { 'title' : '', 'description' : '', 'tags' : '', 'is_public' : '1', 'is_friend' : '0', 'is_family' : '0' } try: opts, args = getopt.getopt(sys.argv[1:], "t:d:T:p:F:A:", ["title=", "description=", "tags=", "public=", "friend=", "family="]) except getopt.GetoptError: usage('invalid options specified') # we don't need no steenking switch for opt, arg in opts: if opt in ('-t', '--title'): optargs['title'] = arg elif opt in ('-d', '--description'): optargs['description'] = arg elif opt in ('-T', '--tags'): optargs['tags'] = ' ' + arg elif opt in ('-p', '--public'): optargs['is_public'] = arg elif opt in ('-F', '--friend'): optargs['is_friend'] = arg elif opt in ('-A', '--family'): optargs['is_family'] = arg if not args: usage('name of file(s) to upload missing') return (optargs, args) def sign(argsdict): """Return a signature.""" keys = argsdict.keys() keys.sort() sigstr = SHAREDSECRET for k in keys: sigstr = sigstr + k + argsdict[k] return md5.new(sigstr).hexdigest() def gettext(nodelist): """Return text inside XML elements.""" retstr = '' for node in nodelist: if node.nodeType == node.TEXT_NODE: retstr = retstr + node.data return retstr def getfrob(): """Get Frob for auth.""" sys.stderr.write('getting frob...\n') reqdict = { 'api_key' : APIKEY, 'method' : 'flickr.auth.getfrob' } frobfd = urllib.urlopen('%s?method=flickr.auth.getfrob&api_key=%s&api_sig=%s' % (RESTURL, APIKEY, sign(reqdict))) parsed = parseString(frobfd.read()) frobfd.close() response = parsed.getElementsByTagName('rsp')[0] if response.getAttribute('stat') == 'fail': raise Oops, response.getElementsByTagName('err')[0].getAttribute('msg') else: frob = gettext(response.getElementsByTagName('frob')[0].childNodes) # print frob return frob.encode() def gettokenfromfile(): """Check if we have a valid saved token.""" # we really should check the token with flickr.auth.checkToken if os.access(TOKENFILE, os.R_OK): tfile = file(TOKENFILE, 'r') line = tfile.readline() tfile.close() (token, date) = line.split() now = time.time() if now - long(date) < 3600: sys.stderr.write('found valid token saved on disk...\n') return token else: return None def gettoken(frob): """Get auth token after the user has authorized us.""" sys.stderr.write('getting auth token...\n') reqdict = { 'api_key' : APIKEY, 'method' : 'flickr.auth.gettoken', 'frob' : frob } tokenfd = urllib.urlopen('%s?method=flickr.auth.gettoken&api_key=%s&frob=%s&api_sig=%s' % (RESTURL, APIKEY, frob, sign(reqdict))) parsed = parseString(tokenfd.read()) tokenfd.close() response = parsed.getElementsByTagName('rsp')[0] if response.getAttribute('stat') == 'ok': token = gettext(response.getElementsByTagName('token')[0].childNodes) # print token os.umask(0077) tfile = file(TOKENFILE, 'w') tfile.write('%s %ld' % (token.encode(), time.time())) tfile.close() return token.encode() else: raise Oops, response.getElementsByTagName('err')[0].getAttribute('msg') # three functions from # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/146306 def post_url(url, fields, files): """POST to a url.""" urlparts = urlparse.urlsplit(url) return post_multipart(urlparts[1], urlparts[2], fields, files) def post_multipart(host, selector, fields, files): """POST multiple MIME parts.""" content_type, body = encode_multipart_formdata(fields, files) hconn = httplib.HTTPConnection(host) headers = { 'User-Agent': USERAGENT, 'Content-Type': content_type } hconn.request('POST', selector, body, headers) res = hconn.getresponse() return res.read() def encode_multipart_formdata(fields, files): """ fields is a sequence of (name, value) elements for regular form fields. files is a sequence of (name, filename, value) elements for data to be uploaded as files Return (content_type, body) ready for httplib.HTTP instance """ boundary = '----------ThIs_Is_tHe_bouNdaRY_$' crlf = '\r\n' body = '' for (key, value) in fields.items(): body = body + '--' + boundary + crlf body = body + 'Content-Disposition: form-data; name="%s"' % key + crlf + crlf body = body + value + crlf for (filename, value) in files: body = body + '--' + boundary + crlf body = body + 'Content-Disposition: form-data; name="photo"; filename="%s"' % filename + crlf body = body + 'Content-Type: %s' % get_content_type(filename) + crlf + crlf body = body + value body = body + crlf + '--' + boundary + '--' + crlf + crlf content_type = 'multipart/form-data; boundary=%s' % boundary return content_type, body def get_content_type(filename): """Guess what the file's type is.""" guess = mimetypes.guess_type(filename)[0] sys.stderr.write('guessing file type is %s\n' % guess) return guess def uploadimage(optsdict, token, fname): """Upload the image.""" sys.stderr.write('uploading %s...\n' % fname) reqdict = { 'api_key' : APIKEY, 'auth_token' : token } for opt in ('title', 'description', 'tags', 'is_public', 'is_friend', 'is_family'): if optsdict[opt]: reqdict[opt] = str(optsdict[opt]) api_sig = sign(reqdict) reqdict['api_sig'] = api_sig ret = post_url('http://flickr.com/services/upload/', reqdict, [[fname, file(fname, 'rb').read()]]) sys.stderr.write(ret + '\n') def main(): """Main branching logic.""" (optsdict, fnames) = getopts() frob = getfrob() token = gettokenfromfile() if not token: reqdict = { 'api_key' : APIKEY, 'perms' : 'write', 'frob' : frob } # where to send the user authurl = 'http://flickr.com/services/auth/?api_key=%s&perms=write&frob=%s&api_sig=%s' % \ (APIKEY, frob, sign(reqdict)) print '''\ you must authorize me. sending you to %s hit return after authorizing me. ''' % authurl webbrowser.open(authurl, autoraise=1) # os.system('firefox -remote "openURL(%s,new-tab)"' % authurl) # os.system('lynx "%s"' % authurl) ignore = sys.stdin.readline() token = gettoken(frob) for fname in fnames: uploadimage(optsdict, token, fname) if __name__ == '__main__': main() # eof