Archive

Archive for February, 2011

Posting photo to wall using Facebook Graph API

February 12, 2011 22 comments

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()