Posting photo to wall using Facebook Graph API
First post in 8 months! I have been busy (no, really! :P)
I have been recently working on a small Facebook app FriendGeo, which is on the verge of being finished as I write. One conclusion: Facebook API is cool, the documentation is NOT. I had my full share of frustration trying to deal with hidden (or so it seems) features and, more painful, restrictions on Facebook API.
Anyway, coming back to the topic, my app needs to post an image to the user’s wall. True to its RESTfulness, the graph API allows you to do so by POSTing to the photos connection of the user. In a perfect world, a simple HTTP POST to https://graph.facebook.com/userid/photos with required parameters (the names of which you are left to guess or google!) should do.
A little bit of tinkering with google search query leads to forum posts like this. Fortunately, the PHP SDK for the Facebook API allows you to upload the photo by using a simple call to setFileUploadSupport. Unfortunately for me, I am using App Engine as my hosting solution and using Python for writing my backend and the official Python SDK for Facebook does not seem to be supporting any such provision. Solution: fall back on Python ‘batteries’ and use raw Python and HTTP.
To demonstrate how to do this with a self contained example I have written this small script that creates a test user and uploads a photo from disk to the test user’s account. The comments in the code below should be enough to guide you through the code.
import sys import os import itertools import mimetypes import mimetools import urllib import urllib2 import cgi try: import json except ImportError: from django.utils import simplejson as json # this class allows you to upload a file to a URL # using pure urllib class MultiPartForm(object): """Accumulate the data to be used when posting a form. source: http://www.doughellmann.com/PyMOTW/urllib2/#uploading-files """ def __init__(self): self.form_fields = [] self.files = [] self.boundary = mimetools.choose_boundary() return def get_content_type(self): return 'multipart/form-data; boundary=%s' % self.boundary def add_field(self, name, value): """Add a simple field to the form data.""" self.form_fields.append((name, value)) return def add_file(self, fieldname, filename, fileHandle, mimetype=None): """Add a file to be uploaded.""" body = fileHandle.read() if mimetype is None: mimetype = mimetypes.guess_type(filename)[0] or 'application/octet-stream' self.files.append((fieldname, filename, mimetype, body)) return def __str__(self): """Return a string representing the form data, including attached files.""" # Build a list of lists, each containing "lines" of the # request. Each part is separated by a boundary string. # Once the list is built, return a string where each # line is separated by '\r\n'. parts = [] part_boundary = '--' + self.boundary # Add the form fields parts.extend( [part_boundary, 'Content-Disposition: form-data; name="%s"' % name, '', value, ] for name, value in self.form_fields ) # Add the files to upload parts.extend( [part_boundary, 'Content-Disposition: file; name="%s"; filename="%s"' % \ (field_name, filename), 'Content-Type: %s' % content_type, '', body, ] for field_name, filename, content_type, body in self.files ) # Flatten the list and add closing boundary marker, # then return CR+LF separated data flattened = list(itertools.chain(*parts)) flattened.append('--' + self.boundary + '--') flattened.append('') flattened = map(str, flattened) return '\r\n'.join(flattened) def get_client_access_token(app_id, app_secret): #create an access token acting as if we are a #desktop client (we in fact are!). app_id and #app_secrets are the parameters that you get #get when you create a new app params = { 'grant_type': 'client_credentials', 'client_id': app_id, 'client_secret': app_secret } response = cgi.parse_qs(urllib.urlopen( "https://graph.facebook.com/oauth/access_token?" + urllib.urlencode(params)).read()) access_token = response["access_token"][-1] return access_token def create_user(app_id, access_token): #create a test user using desktop-client #access token. We need 'publish_stream' and #'user_photos' extended permissions params = { 'access_token': access_token, 'installed': 'true', 'permissions': 'publish_stream,user_photos' } params = urllib.urlencode(params) url = 'https://graph.facebook.com/%s/accounts/test-users'%(app_id) request = urllib2.Request(url, headers = {}, data = params) response = urllib2.urlopen(request) response = response.read() response = json.loads(response) return response def upload_pic(uid, access_token, pic_caption, pic_file_name, pic_file): #using access_token for the newly created test user upload #an image file from disk (or any other file like source) to #the user's account. #pic_caption: the caption to the pic #pic_file_name: name of the file (used only to guess mimetype) #pic_file: a file like object representing the file (one can #open a url and use the response here if the file to be uploaded #is on the web (and not on the disk) form = MultiPartForm() form.add_field('access_token', access_token) form.add_field('caption', pic_caption) form.add_file('source', pic_file_name, pic_file) request = urllib2.Request('https://graph.facebook.com/%s/photos'%(uid)) body = str(form) request.add_header('Content-type', form.get_content_type()) request.add_header('Content-length', len(body)) request.add_data(body) return urllib2.urlopen(request).read() def do(): if not len(sys.argv) == 5: print "usage: python %s <app_id> <app_secret> <caption> <file_path>"%(sys.argv[0]) return app_id = sys.argv[1] app_secret = sys.argv[2] p_caption = sys.argv[3] file_path = sys.argv[4] client_access_token = get_client_access_token(app_id, app_secret) user_info = create_user(app_id, client_access_token) u_id = user_info['id'] print 'created test user account with id: %s'%(u_id) u_access_token = user_info['access_token'] user_login_url = user_info['login_url'] p_file_name = os.path.basename(file_path) p_file = open(file_path) upload_pic(u_id, u_access_token, p_caption, p_file_name, p_file) print 'login as the test user and view the uploaded pic: %s'%(user_login_url) if __name__ == '__main__': do()
Recent Comments