#!/usr/bin/env python """Flickr uploader. I wrote this in 2005, and didn't use any of the available python APIs. 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 # replace the following two lines with your key/secret # login to flickr and go to http://www.flickr.com/services/api/key.gne apiKey = '45584dff059489d06fdf471a5b7d9378' sharedSecret = '46a396cf11c5df3c' restUrl = 'http://www.flickr.com/services/rest/' tokenFile = os.environ['HOME'] + '/.flickr-upload-token' def usage(msg=None): progname = sys.argv[0] if msg: sys.stderr.write(msg + '\n') sys.stderr.write('''\ usage: %s [options] pic.jpg [pic2.jpg 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(): 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 o, a in opts: if o in ('-t', '--title'): optArgs['title'] = a elif o in ('-d', '--description'): optArgs['description'] = a elif o in ('-T', '--tags'): optArgs['tags'] = ' ' + a elif o in ('-p', '--public'): optArgs['is_public'] = a elif o in ('-F', '--friend'): optArgs['is_friend'] = a elif o in ('-A', '--family'): optArgs['is_family'] = a if not args: usage('name of file to upload missing') return (optArgs, args) def sign(argsDict): """Return a signature.""" keys = argsDict.keys() keys.sort() str = sharedSecret for k in keys: str = str + k + argsDict[k] return md5.new(str).hexdigest() def getFrob(): """Get Frob for auth.""" sys.stderr.write('getting frob...\n') d = { 'api_key' : apiKey, 'method' : 'flickr.auth.getFrob' } u = urllib.urlopen('%s?method=flickr.auth.getFrob&api_key=%s&api_sig=%s' % (restUrl, apiKey, sign(d))) lines = u.readlines() u.close() if lines[1] == '\n': return lines[2].replace('', '').replace('', '').strip() else: raise 'oops: %s' % (lines) 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): f = file(tokenFile, 'r') line = f.readline() f.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') d = { 'api_key' : apiKey, 'method' : 'flickr.auth.getToken', 'frob' : frob } u = urllib.urlopen('%s?method=flickr.auth.getToken&api_key=%s&frob=%s&api_sig=%s' % (restUrl, apiKey, frob, sign(d))) lines = u.readlines() u.close() if lines[1] == '\n': for l in lines[1:]: if l.find('') != -1: token = l.replace('', '').replace('', '').strip() os.umask(0077) f = file(tokenFile, 'w') f.write('%s %ld' % (token, time.time())) f.close() return token else: raise 'oops: %s' % (lines) # three functions from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/146306 def post_url(url, fields, files): urlparts = urlparse.urlsplit(url) return post_multipart(urlparts[1], urlparts[2], fields, files) def post_multipart(host, selector, fields, files): content_type, body = encode_multipart_formdata(fields, files) h = httplib.HTTPConnection(host) headers = { 'User-Agent': 'UploaderForToshok/2005-10-05', 'Content-Type': content_type } h.request('POST', selector, body, headers) res = h.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 + '\r\n' body = body + 'Content-Disposition: form-data; name="%s"\r\n\r\n' % key body = body + value + '\r\n' for (filename, value) in files: body = body + '--' + BOUNDARY + '\r\n' body = body + 'Content-Disposition: form-data; name="photo"; filename="%s"\r\n' % (filename) body = body + 'Content-Type: %s\r\n\r\n' % get_content_type(filename) body = body + value body = body + '\r\n--' + BOUNDARY + '--\r\n\r\n' content_type = 'multipart/form-data; boundary=%s' % BOUNDARY return content_type, body def get_content_type(filename): 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)) d = { 'api_key' : apiKey, 'auth_token' : token } for o in ('title', 'description', 'tags', 'is_public', 'is_friend', 'is_family'): if optsDict[o]: d[o] = str(optsDict[o]) api_sig = sign(d) d['api_sig'] = api_sig ret = post_url('http://flickr.com/services/upload/', d, [[fname, file(fname, 'rb').read()]]) sys.stderr.write(ret + '\n') def main(): (optsDict, fnames) = getopts() frob = getFrob() token = getTokenFromFile() if not token: d = { 'api_key' : apiKey, 'perms' : 'write', 'frob' : frob } # url to send the user u = 'http://flickr.com/services/auth/?api_key=%s&perms=write&frob=%s&api_sig=%s' % \ (apiKey, frob, sign(d)) print 'you must authorize me. sending you to\n\n%s\n' % (u) print 'hit return after authorizing me.' os.system('firefox -remote "openURL(%s,new-tab)"' % (u)) # os.system('lynx "%s"' % (u)) ignore = sys.stdin.readline() token = getToken(frob) for fname in fnames: uploadImage(optsDict, token, fname) if __name__ == '__main__': main()