diff --git a/readme.md b/readme.md index 814cce9..d0b3c0f 100644 --- a/readme.md +++ b/readme.md @@ -14,6 +14,12 @@ Basic usage: friends = graph.get_connections("me", "friends") graph.put_object("me", "feed", message="I am writing on my wall!") +Photo uploads: + + graph = facebook.GraphAPI(oauth_access_token) + tags = json.dumps([{'x':50, 'y':50, tag_uid:12345}, {'x':10, 'y':60, tag_text:'a turtle'}]) + graph.put_photo(album_id_or_None, source=open('img.jpg'), message="Cool photo!", tags=tags) + If you are using the module within a web application with the [JavaScript SDK](http://github.com/facebook/connect-js), you can also use the module to use Facebook for login, parsing the cookie set by the JavaScript SDK @@ -26,4 +32,5 @@ profile of the logged in user with: profile = graph.get_object("me") friends = graph.get_connections("me", "friends") + You can see a full AppEngine example application in examples/appengine. diff --git a/src/facebook.py b/src/facebook.py index 6a444fc..13e897d 100644 --- a/src/facebook.py +++ b/src/facebook.py @@ -36,7 +36,7 @@ import cgi import hashlib import time -import urllib +import urllib, urllib2 # Find a JSON parser try: @@ -152,6 +152,84 @@ def delete_object(self, id): """Deletes the object with the given ID from the graph.""" self.request(id, post_args={"method": "delete"}) + def put_album(self, object_id, **kwargs): + """creates an album under the specified object (user, page etc) + object_id=None posts to /me/albums. + returns a dictionary that contains the album id + """ + + object_id = object_id or "me" + assert self.access_token, "Write operations require an access token" + kwargs['access_token'] = self.access_token + content_type, body = self._encode_multipart_form(kwargs) + req = urllib2.Request("https://graph.facebook.com/%s/albums" % object_id, data=body) + req.add_header('Content-Type', content_type) + try: + data = urllib2.urlopen(req).read() + except urllib2.HTTPError as e: + data = e.read() # Facebook sends OAuth errors as 400, and urllib2 throws an exception, we want a GraphAPIError + try: + response = _parse_json(data) + if response.get("error"): + raise GraphAPIError(response["error"].get("code", 1), + response["error"]["message"]) + except ValueError: + response = data + + return response + + def put_photo(self, album_id=None, **kwargs): + """Uploads an image using multipart/form-data + album_id=None posts to /me/photos which uses or creates and uses + an album for your application. + """ + object_id = album_id or "me" + #it would have been nice to reuse self.request; but multipart is messy in urllib + kwargs['access_token'] = self.access_token + content_type, body = self._encode_multipart_form(kwargs) + req = urllib2.Request("https://graph.facebook.com/%s/photos" % object_id, data=body) + req.add_header('Content-Type', content_type) + try: + data = urllib2.urlopen(req).read() + except urllib2.HTTPError as e: + data = e.read() # Facebook sends OAuth errors as 400, and urllib2 throws an exception, we want a GraphAPIError + try: + response = _parse_json(data) + if response.get("error"): + raise GraphAPIError(response["error"].get("code", 1), + response["error"]["message"]) + except ValueError: + response = data + + return response + + # based on: http://code.activestate.com/recipes/146306/ + def _encode_multipart_form(self, fields): + """Fields are a dict of form name-> value + For files, value should be a file object. + Other file-like objects might work and a fake name will be chosen. + Return (content_type, body) ready for httplib.HTTP instance + """ + BOUNDARY = '----------ThIs_Is_tHe_bouNdaRY_$' + CRLF = '\r\n' + L = [] + for (key, value) in fields.items(): + L.append('--' + BOUNDARY) + if hasattr(value, 'read') and callable(value.read): + filename = getattr(value,'name','%s.jpg' % key) + L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename)) + L.append('Content-Type: image/jpeg') + value = value.read() + else: + L.append('Content-Disposition: form-data; name="%s"' % key) + L.append('') + L.append(value) + L.append('--' + BOUNDARY + '--') + L.append('') + body = CRLF.join(L) + content_type = 'multipart/form-data; boundary=%s' % BOUNDARY + return content_type, body + def request(self, path, args=None, post_args=None): """Fetches the given path in the Graph API.