3 # pyfacebook - Python bindings for the Facebook API
5 # Copyright (c) 2008, Samuel Cormier-Iijima
8 # Redistribution and use in source and binary forms, with or without
9 # modification, are permitted provided that the following conditions are met:
10 # * Redistributions of source code must retain the above copyright
11 # notice, this list of conditions and the following disclaimer.
12 # * Redistributions in binary form must reproduce the above copyright
13 # notice, this list of conditions and the following disclaimer in the
14 # documentation and/or other materials provided with the distribution.
15 # * Neither the name of the author nor the names of its contributors may
16 # be used to endorse or promote products derived from this software
17 # without specific prior written permission.
19 # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS``AS IS'' AND ANY
20 # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 # DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
23 # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 Python bindings for the Facebook API (pyfacebook - http://code.google.com/p/pyfacebook)
33 PyFacebook is a client library that wraps the Facebook API.
35 For more information, see
37 Home Page: http://code.google.com/p/pyfacebook
38 Developer Wiki: http://wiki.developers.facebook.com/index.php/Python
39 Facebook IRC Channel: #facebook on irc.freenode.net
41 PyFacebook can use simplejson if it is installed, which
42 is much faster than XML and also uses less bandwith. Go to
43 http://undefined.org/python/#simplejson to download it, or do
44 apt-get install python-simplejson on a Debian-like system.
59 # try to use simplejson first, otherwise fallback to XML
60 RESPONSE_FORMAT = 'JSON'
65 import json as simplejson
68 from django.utils import simplejson
71 import jsonlib as simplejson
73 except (ImportError, AttributeError):
74 from xml.dom import minidom
75 RESPONSE_FORMAT = 'XML'
77 # support Google App Engine. GAE does not have a working urllib.urlopen.
79 from google.appengine.api import urlfetch
81 def urlread(url, data=None, headers=None):
84 headers = {"Content-type": "application/x-www-form-urlencoded"}
85 method = urlfetch.POST
91 result = urlfetch.fetch(url, method=method,
92 payload=data, headers=headers)
94 if result.status_code == 200:
97 raise urllib2.URLError("fetch error url=%s, code=%d" % (url, result.status_code))
100 def urlread(url, data=None):
101 res = urllib2.urlopen(url, data=data)
104 __all__ = ['Facebook']
108 FACEBOOK_URL = 'http://api.facebook.com/restserver.php'
109 FACEBOOK_SECURE_URL = 'https://api.facebook.com/restserver.php'
111 class json(object): pass
113 # simple IDL for the Facebook API
117 ('application_id', int, ['optional']),
118 ('application_api_key', str, ['optional']),
119 ('application_canvas_name ', str,['optional']),
126 ('integration_point_name', str, []),
132 'publishStoryToUser': [
134 ('body', str, ['optional']),
135 ('image_1', str, ['optional']),
136 ('image_1_link', str, ['optional']),
137 ('image_2', str, ['optional']),
138 ('image_2_link', str, ['optional']),
139 ('image_3', str, ['optional']),
140 ('image_3_link', str, ['optional']),
141 ('image_4', str, ['optional']),
142 ('image_4_link', str, ['optional']),
143 ('priority', int, ['optional']),
146 'publishActionOfUser': [
148 ('body', str, ['optional']),
149 ('image_1', str, ['optional']),
150 ('image_1_link', str, ['optional']),
151 ('image_2', str, ['optional']),
152 ('image_2_link', str, ['optional']),
153 ('image_3', str, ['optional']),
154 ('image_3_link', str, ['optional']),
155 ('image_4', str, ['optional']),
156 ('image_4_link', str, ['optional']),
157 ('priority', int, ['optional']),
160 'publishTemplatizedAction': [
161 ('title_template', str, []),
162 ('page_actor_id', int, ['optional']),
163 ('title_data', json, ['optional']),
164 ('body_template', str, ['optional']),
165 ('body_data', json, ['optional']),
166 ('body_general', str, ['optional']),
167 ('image_1', str, ['optional']),
168 ('image_1_link', str, ['optional']),
169 ('image_2', str, ['optional']),
170 ('image_2_link', str, ['optional']),
171 ('image_3', str, ['optional']),
172 ('image_3_link', str, ['optional']),
173 ('image_4', str, ['optional']),
174 ('image_4_link', str, ['optional']),
175 ('target_ids', list, ['optional']),
178 'registerTemplateBundle': [
179 ('one_line_story_templates', json, []),
180 ('short_story_templates', json, ['optional']),
181 ('full_story_template', json, ['optional']),
182 ('action_links', json, ['optional']),
185 'deactivateTemplateBundleByID': [
186 ('template_bundle_id', int, []),
189 'getRegisteredTemplateBundles': [],
191 'getRegisteredTemplateBundleByID': [
192 ('template_bundle_id', str, []),
195 'publishUserAction': [
196 ('template_bundle_id', int, []),
197 ('template_data', json, ['optional']),
198 ('target_ids', list, ['optional']),
199 ('body_general', str, ['optional']),
218 ('flid', int, ['optional']),
226 # notifications methods
231 ('to_ids', list, []),
232 ('notification', str, []),
233 ('email', str, ['optional']),
234 ('type', str, ['optional']),
238 ('to_ids', list, []),
240 ('content', str, []),
242 ('invite', bool, []),
246 ('recipients', list, []),
247 ('subject', str, []),
248 ('text', str, ['optional']),
249 ('fbml', str, ['optional']),
256 ('markup', str, ['optional']),
257 ('uid', int, ['optional']),
258 ('profile', str, ['optional']),
259 ('profile_action', str, ['optional']),
260 ('mobile_fbml', str, ['optional']),
261 ('profile_main', str, ['optional']),
265 ('uid', int, ['optional']),
266 ('type', int, ['optional']),
272 ('info_fields', json, []),
282 ('options', json, []),
294 ('fields', list, [('default', ['name'])]),
299 ('fields', list, [('default', ['uid'])]),
302 'getLoggedInUser': [],
306 'hasAppPermission': [
307 ('ext_perm', str, []),
308 ('uid', int, ['optional']),
314 ('status_includes_verb', bool, ['optional']),
315 ('uid', int, ['optional']),
322 ('uid', int, ['optional']),
323 ('eids', list, ['optional']),
324 ('start_time', int, ['optional']),
325 ('end_time', int, ['optional']),
326 ('rsvp_status', str, ['optional']),
334 ('event_info', json, []),
348 ('uid', int, ['optional']),
349 ('gids', list, ['optional']),
357 # marketplace methods
360 ('listing_id', int, []),
361 ('show_on_profile', bool, []),
362 ('listing_attrs', str, []),
368 ('listing_ids', list, []),
372 'getSubCategories': [
373 ('category', str, []),
377 ('listing_id', int, []),
382 ('category', str, ['optional']),
383 ('subcategory', str, ['optional']),
384 ('query', str, ['optional']),
391 ('page_ids', list, ['optional']),
392 ('uid', int, ['optional']),
396 ('page_id', int, []),
400 ('page_id', int, []),
404 ('page_id', int, []),
413 ('tag_uid', int, [('default', 0)]),
414 ('tag_text', str, [('default', '')]),
415 ('x', float, [('default', 50)]),
416 ('y', float, [('default', 50)]),
417 ('tags', str, ['optional']),
422 ('location', str, ['optional']),
423 ('description', str, ['optional']),
427 ('subj_id', int, ['optional']),
428 ('aid', int, ['optional']),
429 ('pids', list, ['optional']),
433 ('uid', int, ['optional']),
434 ('aids', list, ['optional']),
466 ('message', str, []),
467 ('session_id', int, []),
468 ('req_session', bool, []),
482 ('expires', int, ['optional']),
483 ('path', str, ['optional']),
490 ('accounts', json, []),
494 ('email_hashes', json, []),
497 'getUnconnectedFriendsCount': [
503 """Represents a "namespace" of Facebook API calls."""
505 def __init__(self, client, name):
506 self._client = client
509 def __call__(self, method=None, args=None, add_session_args=True):
510 # for Django templates
515 self._client._add_session_args(args)
517 return self._client('%s.%s' % (self._name, method), args)
520 # generate the Facebook proxies
521 def __generate_proxies():
522 for namespace in METHODS:
525 for method in METHODS[namespace]:
529 for param_name, param_type, param_options in METHODS[namespace][method]:
532 for option in param_options:
533 if isinstance(option, tuple) and option[0] == 'default':
534 if param_type == list:
535 param = '%s=None' % param_name
536 body.append('if %s is None: %s = %s' % (param_name, param_name, repr(option[1])))
538 param = '%s=%s' % (param_name, repr(option[1]))
540 if param_type == json:
541 # we only jsonify the argument if it's a list or a dict, for compatibility
542 body.append('if isinstance(%s, list) or isinstance(%s, dict): %s = simplejson.dumps(%s)' % ((param_name,) * 4))
544 if 'optional' in param_options:
545 param = '%s=None' % param_name
546 body.append('if %s is not None: args[\'%s\'] = %s' % (param_name, param_name, param_name))
548 body.append('args[\'%s\'] = %s' % (param_name, param_name))
552 # simple docstring to refer them to Facebook API docs
553 body.insert(0, '"""Facebook API call. See http://developers.facebook.com/documentation.php?v=1.0&method=%s.%s"""' % (namespace, method))
555 body.insert(0, 'def %s(%s):' % (method, ', '.join(params)))
557 body.append('return self(\'%s\', args)' % method)
559 exec('\n '.join(body))
561 methods[method] = eval(method)
563 proxy = type('%sProxy' % namespace.title(), (Proxy, ), methods)
565 globals()[proxy.__name__] = proxy
571 class FacebookError(Exception):
572 """Exception class for errors received from Facebook."""
574 def __init__(self, code, msg, args=None):
580 return 'Error %s: %s' % (self.code, self.msg)
583 class AuthProxy(Proxy):
584 """Special proxy for facebook.auth."""
586 def getSession(self):
587 """Facebook API call. See http://developers.facebook.com/documentation.php?v=1.0&method=auth.getSession"""
590 args['auth_token'] = self._client.auth_token
591 except AttributeError:
592 raise RuntimeError('Client does not have auth_token set.')
593 result = self._client('%s.getSession' % self._name, args)
594 self._client.session_key = result['session_key']
595 self._client.uid = result['uid']
596 self._client.secret = result.get('secret')
597 self._client.session_key_expires = result['expires']
600 def createToken(self):
601 """Facebook API call. See http://developers.facebook.com/documentation.php?v=1.0&method=auth.createToken"""
602 token = self._client('%s.createToken' % self._name)
603 self._client.auth_token = token
607 class FriendsProxy(FriendsProxy):
608 """Special proxy for facebook.friends."""
610 def get(self, **kwargs):
611 """Facebook API call. See http://developers.facebook.com/documentation.php?v=1.0&method=friends.get"""
612 if not kwargs.get('flid') and self._client._friends:
613 return self._client._friends
614 return super(FriendsProxy, self).get(**kwargs)
617 class PhotosProxy(PhotosProxy):
618 """Special proxy for facebook.photos."""
620 def upload(self, image, aid=None, caption=None, size=(604, 1024), filename=None):
621 """Facebook API call. See http://developers.facebook.com/documentation.php?v=1.0&method=photos.upload
623 size -- an optional size (width, height) to resize the image to before uploading. Resizes by default
624 to Facebook's maximum display width of 604.
631 if caption is not None:
632 args['caption'] = caption
634 args = self._client._build_post_args('facebook.photos.upload', self._client._add_session_args(args))
637 import cStringIO as StringIO
641 # check for a filename specified...if the user is passing binary data in
642 # image then a filename will be specified
647 data = StringIO.StringIO(open(image, 'rb').read())
649 img = Image.open(image)
651 img.thumbnail(size, Image.ANTIALIAS)
652 data = StringIO.StringIO()
653 img.save(data, img.format)
655 # there was a filename specified, which indicates that image was not
656 # the path to an image file but rather the binary data of a file
657 data = StringIO.StringIO(image)
660 content_type, body = self.__encode_multipart_formdata(list(args.iteritems()), [(image, data)])
661 urlinfo = urlparse.urlsplit(self._client.facebook_url)
663 h = httplib.HTTP(urlinfo[1])
664 h.putrequest('POST', urlinfo[2])
665 h.putheader('Content-Type', content_type)
666 h.putheader('Content-Length', str(len(body)))
667 h.putheader('MIME-Version', '1.0')
668 h.putheader('User-Agent', 'PyFacebook Client Library')
675 raise Exception('Error uploading photo: Facebook returned HTTP %s (%s)' % (reply[0], reply[1]))
677 response = h.file.read()
679 # sending the photo failed, perhaps we are using GAE
681 from google.appengine.api import urlfetch
684 response = urlread(url=self._client.facebook_url,data=body,headers={'POST':urlinfo[2],'Content-Type':content_type,'MIME-Version':'1.0'})
685 except urllib2.URLError:
686 raise Exception('Error uploading photo: Facebook returned %s' % (response))
688 # could not import from google.appengine.api, so we are not running in GAE
689 raise Exception('Error uploading photo.')
691 return self._client._parse_response(response, 'facebook.photos.upload')
694 def __encode_multipart_formdata(self, fields, files):
695 """Encodes a multipart/form-data message to upload an image."""
696 boundary = '-------tHISiStheMulTIFoRMbOUNDaRY'
700 for (key, value) in fields:
701 l.append('--' + boundary)
702 l.append('Content-Disposition: form-data; name="%s"' % str(key))
705 for (filename, value) in files:
706 l.append('--' + boundary)
707 l.append('Content-Disposition: form-data; filename="%s"' % (str(filename), ))
708 l.append('Content-Type: %s' % self.__get_content_type(filename))
710 l.append(value.getvalue())
711 l.append('--' + boundary + '--')
714 content_type = 'multipart/form-data; boundary=%s' % boundary
715 return content_type, body
718 def __get_content_type(self, filename):
719 """Returns a guess at the MIME type of the file from the filename."""
720 return str(mimetypes.guess_type(filename)[0]) or 'application/octet-stream'
723 class Facebook(object):
725 Provides access to the Facebook API.
730 True if the user has added this application.
733 Your API key, as set in the constructor.
736 Your application's name, i.e. the APP_NAME in http://apps.facebook.com/APP_NAME/ if
737 this is for an internal web application. Optional, but useful for automatic redirects
741 The auth token that Facebook gives you, either with facebook.auth.createToken,
742 or through a GET parameter.
745 The path of the callback set in the Facebook app settings. If your callback is set
746 to http://www.example.com/facebook/callback/, this should be '/facebook/callback/'.
747 Optional, but useful for automatic redirects back to the same page after login.
750 True if this is a desktop app, False otherwise. Used for determining how to
754 The url to use for Facebook requests.
757 The url to use for secure Facebook requests.
760 True if the current request is for a canvas page.
763 True if this Facebook object is for an internal application (one that can be added on Facebook)
766 Set to the page_id of the current page (if any)
769 Secret that is used after getSession for desktop apps.
772 Your application's secret key, as set in the constructor.
775 The current session key. Set automatically by auth.getSession, but can be set
776 manually for doing infinite sessions.
779 The UNIX time of when this session key expires, or 0 if it never expires.
782 After a session is created, you can get the user's UID with this variable. Set
783 automatically by auth.getSession.
785 ----------------------------------------------------------------------
789 def __init__(self, api_key, secret_key, auth_token=None, app_name=None, callback_path=None, internal=None, proxy=None, facebook_url=None, facebook_secure_url=None):
791 Initializes a new Facebook object which provides wrappers for the Facebook API.
793 If this is a desktop application, the next couple of steps you might want to take are:
795 facebook.auth.createToken() # create an auth token
796 facebook.login() # show a browser window
797 wait_login() # somehow wait for the user to log in
798 facebook.auth.getSession() # get a session key
800 For web apps, if you are passed an auth_token from Facebook, pass that in as a named parameter.
803 facebook.auth.getSession()
806 self.api_key = api_key
807 self.secret_key = secret_key
808 self.session_key = None
809 self.session_key_expires = None
810 self.auth_token = auth_token
814 self.in_canvas = False
816 self.app_name = app_name
817 self.callback_path = callback_path
818 self.internal = internal
821 if facebook_url is None:
822 self.facebook_url = FACEBOOK_URL
824 self.facebook_url = facebook_url
825 if facebook_secure_url is None:
826 self.facebook_secure_url = FACEBOOK_SECURE_URL
828 self.facebook_secure_url = facebook_secure_url
830 for namespace in METHODS:
831 self.__dict__[namespace] = eval('%sProxy(self, \'%s\')' % (namespace.title(), 'facebook.%s' % namespace))
833 self.auth = AuthProxy(self, 'facebook.auth')
836 def _hash_args(self, args, secret=None):
837 """Hashes arguments by joining key=value pairs, appending a secret, and then taking the MD5 hex digest."""
839 # fix for UnicodeEncodeError
840 hasher = md5.new(''.join(['%s=%s' % (isinstance(x, unicode) and x.encode("utf-8") or x, isinstance(args[x], unicode) and args[x].encode("utf-8") or args[x]) for x in sorted(args.keys())]))
842 hasher.update(secret)
844 hasher.update(self.secret)
846 hasher.update(self.secret_key)
847 return hasher.hexdigest()
850 def _parse_response_item(self, node):
851 """Parses an XML response node from Facebook."""
852 if node.nodeType == node.DOCUMENT_NODE and \
853 node.childNodes[0].hasAttributes() and \
854 node.childNodes[0].hasAttribute('list') and \
855 node.childNodes[0].getAttribute('list') == "true":
856 return {node.childNodes[0].nodeName: self._parse_response_list(node.childNodes[0])}
857 elif node.nodeType == node.ELEMENT_NODE and \
858 node.hasAttributes() and \
859 node.hasAttribute('list') and \
860 node.getAttribute('list')=="true":
861 return self._parse_response_list(node)
862 elif len(filter(lambda x: x.nodeType == x.ELEMENT_NODE, node.childNodes)) > 0:
863 return self._parse_response_dict(node)
865 return ''.join(node.data for node in node.childNodes if node.nodeType == node.TEXT_NODE)
868 def _parse_response_dict(self, node):
869 """Parses an XML dictionary response node from Facebook."""
871 for item in filter(lambda x: x.nodeType == x.ELEMENT_NODE, node.childNodes):
872 result[item.nodeName] = self._parse_response_item(item)
873 if node.nodeType == node.ELEMENT_NODE and node.hasAttributes():
874 if node.hasAttribute('id'):
875 result['id'] = node.getAttribute('id')
879 def _parse_response_list(self, node):
880 """Parses an XML list response node from Facebook."""
882 for item in filter(lambda x: x.nodeType == x.ELEMENT_NODE, node.childNodes):
883 result.append(self._parse_response_item(item))
887 def _check_error(self, response):
888 """Checks if the given Facebook response is an error, and then raises the appropriate exception."""
889 if type(response) is dict and response.has_key('error_code'):
890 raise FacebookError(response['error_code'], response['error_msg'], response['request_args'])
893 def _build_post_args(self, method, args=None):
894 """Adds to args parameters that are necessary for every call to the API."""
898 for arg in args.items():
899 if type(arg[1]) == list:
900 args[arg[0]] = ','.join(str(a) for a in arg[1])
901 elif type(arg[1]) == unicode:
902 args[arg[0]] = arg[1].encode("UTF-8")
903 elif type(arg[1]) == bool:
904 args[arg[0]] = str(arg[1]).lower()
906 args['method'] = method
907 args['api_key'] = self.api_key
909 args['format'] = RESPONSE_FORMAT
910 args['sig'] = self._hash_args(args)
915 def _add_session_args(self, args=None):
916 """Adds 'session_key' and 'call_id' to args, which are used for API calls that need sessions."""
920 if not self.session_key:
922 #some calls don't need a session anymore. this might be better done in the markup
923 #raise RuntimeError('Session key not set. Make sure auth.getSession has been called.')
925 args['session_key'] = self.session_key
926 args['call_id'] = str(int(time.time() * 1000))
931 def _parse_response(self, response, method, format=None):
932 """Parses the response according to the given (optional) format, which should be either 'JSON' or 'XML'."""
934 format = RESPONSE_FORMAT
937 result = simplejson.loads(response)
939 self._check_error(result)
940 elif format == 'XML':
941 dom = minidom.parseString(response)
942 result = self._parse_response_item(dom)
945 if 'error_response' in result:
946 self._check_error(result['error_response'])
948 result = result[method[9:].replace('.', '_') + '_response']
950 raise RuntimeError('Invalid format specified.')
955 def hash_email(self, email):
957 Hash an email address in a format suitable for Facebook Connect.
960 email = email.lower().strip()
962 struct.unpack("I", struct.pack("i", binascii.crc32(email)))[0],
963 hashlib.md5(email).hexdigest(),
967 def unicode_urlencode(self, params):
970 A unicode aware version of urllib.urlencode.
972 if isinstance(params, dict):
973 params = params.items()
974 return urllib.urlencode([(k, isinstance(v, unicode) and v.encode('utf-8') or v)
978 def __call__(self, method=None, args=None, secure=False):
979 """Make a call to Facebook's REST server."""
980 # for Django templates, if this object is called without any arguments
981 # return the object itself
986 # fix for bug of UnicodeEncodeError
987 post_data = self.unicode_urlencode(self._build_post_args(method, args))
991 proxy_handler = urllib2.ProxyHandler(self.proxy)
992 opener = urllib2.build_opener(proxy_handler)
994 response = opener.open(self.facebook_secure_url, post_data).read()
996 response = opener.open(self.facebook_url, post_data).read()
999 response = urlread(self.facebook_secure_url, post_data)
1001 response = urlread(self.facebook_url, post_data)
1004 return self._parse_response(response, method)
1008 def get_url(self, page, **args):
1010 Returns one of the Facebook URLs (www.facebook.com/SOMEPAGE.php).
1011 Named arguments are passed as GET query string parameters.
1014 print 'page, args:', page, args
1015 return 'http://www.facebook.com/%s.php?%s' % (page, urllib.urlencode(args))
1018 def get_app_url(self, path=''):
1020 Returns the URL for this app's canvas page, according to app_name.
1023 return 'http://apps.facebook.com/%s/%s' % (self.app_name, path)
1026 def get_add_url(self, next=None):
1028 Returns the URL that the user should be redirected to in order to add the application.
1031 args = {'api_key': self.api_key, 'v': '1.0'}
1033 if next is not None:
1036 return self.get_url('install', **args)
1039 def get_authorize_url(self, next=None, next_cancel=None):
1041 Returns the URL that the user should be redirected to in order to
1042 authorize certain actions for application.
1045 args = {'api_key': self.api_key, 'v': '1.0'}
1047 if next is not None:
1050 if next_cancel is not None:
1051 args['next_cancel'] = next_cancel
1053 return self.get_url('authorize', **args)
1056 def get_login_url(self, next=None, popup=False, canvas=False):
1058 Returns the URL that the user should be redirected to in order to login.
1060 next -- the URL that Facebook should redirect to after login
1063 args = {'api_key': self.api_key, 'v': '1.0'}
1065 if next is not None:
1074 if self.auth_token is not None:
1075 args['auth_token'] = self.auth_token
1077 return self.get_url('login', **args)
1080 def login(self, popup=False):
1081 """Open a web browser telling the user to login to Facebook."""
1083 webbrowser.open(self.get_login_url(popup=popup))
1086 def get_ext_perm_url(self, ext_perm, next=None, popup=False):
1088 Returns the URL that the user should be redirected to in order to grant an extended permission.
1090 ext_perm -- the name of the extended permission to request
1091 next -- the URL that Facebook should redirect to after login
1094 args = {'ext_perm': ext_perm, 'api_key': self.api_key, 'v': '1.0'}
1096 if next is not None:
1102 return self.get_url('authorize', **args)
1105 def request_extended_permission(self, ext_perm, popup=False):
1106 """Open a web browser telling the user to grant an extended permission."""
1108 webbrowser.open(self.get_ext_perm_url(ext_perm, popup=popup))
1111 def check_session(self, request):
1113 Checks the given Django HttpRequest for Facebook parameters such as
1114 POST variables or an auth token. If the session is valid, returns True
1115 and this object can now be used to access the Facebook API. Otherwise,
1116 it returns False, and the application should take the appropriate action
1117 (either log the user in or have him add the application).
1120 self.in_canvas = (request.POST.get('fb_sig_in_canvas') == '1')
1122 if self.session_key and (self.uid or self.page_id):
1125 if request.method == 'POST':
1126 params = self.validate_signature(request.POST)
1128 if 'installed' in request.GET:
1131 if 'fb_page_id' in request.GET:
1132 self.page_id = request.GET['fb_page_id']
1134 if 'auth_token' in request.GET:
1135 self.auth_token = request.GET['auth_token']
1138 self.auth.getSession()
1139 except FacebookError, e:
1140 self.auth_token = None
1145 params = self.validate_signature(request.GET)
1148 # first check if we are in django - to check cookies
1149 if hasattr(request, 'COOKIES'):
1150 params = self.validate_cookie_signature(request.COOKIES)
1152 # if not, then we might be on GoogleAppEngine, check their request object cookies
1153 if hasattr(request,'cookies'):
1154 params = self.validate_cookie_signature(request.cookies)
1159 if params.get('in_canvas') == '1':
1160 self.in_canvas = True
1162 if params.get('added') == '1':
1165 if params.get('expires'):
1166 self.session_key_expires = int(params['expires'])
1168 if 'friends' in params:
1169 if params['friends']:
1170 self._friends = params['friends'].split(',')
1174 if 'session_key' in params:
1175 self.session_key = params['session_key']
1176 if 'user' in params:
1177 self.uid = params['user']
1178 elif 'page_id' in params:
1179 self.page_id = params['page_id']
1182 elif 'profile_session_key' in params:
1183 self.session_key = params['profile_session_key']
1184 if 'profile_user' in params:
1185 self.uid = params['profile_user']
1194 def validate_signature(self, post, prefix='fb_sig', timeout=None):
1196 Validate parameters passed to an internal Facebook app from Facebook.
1201 if prefix not in args:
1206 if timeout and '%s_time' % prefix in post and time.time() - float(post['%s_time' % prefix]) > timeout:
1209 args = dict([(key[len(prefix + '_'):], value) for key, value in args.items() if key.startswith(prefix)])
1211 hash = self._hash_args(args)
1213 if hash == post[prefix]:
1218 def validate_cookie_signature(self, cookies):
1220 Validate parameters passed by cookies, namely facebookconnect or js api.
1222 if not self.api_key in cookies.keys():
1227 for k in sorted(cookies.keys()):
1228 if k.startswith(self.api_key+"_"):
1230 params[k.replace(self.api_key+"_","")] = cookies[k]
1233 vals = ''.join(['%s=%s' % (x.replace(self.api_key+"_",""), cookies[x]) for x in sigkeys])
1234 hasher = md5.new(vals)
1236 hasher.update(self.secret_key)
1237 digest = hasher.hexdigest()
1238 if digest == cookies[self.api_key]:
1246 if __name__ == '__main__':
1247 # sample desktop application
1252 facebook = Facebook(api_key, secret_key)
1254 facebook.auth.createToken()
1257 # Set popup=True if you want login without navigational elements
1260 # Login to the window, then press enter
1261 print 'After logging in, press enter...'
1264 facebook.auth.getSession()
1265 print 'Session Key: ', facebook.session_key
1266 print 'Your UID: ', facebook.uid
1268 info = facebook.users.getInfo([facebook.uid], ['name', 'birthday', 'affiliations', 'sex'])[0]
1270 print 'Your Name: ', info['name']
1271 print 'Your Birthday: ', info['birthday']
1272 print 'Your Gender: ', info['sex']
1274 friends = facebook.friends.get()
1275 friends = facebook.users.getInfo(friends[0:5], ['name', 'birthday', 'relationship_status'])
1277 for friend in friends:
1278 print friend['name'], 'has a birthday on', friend['birthday'], 'and is', friend['relationship_status']
1280 arefriends = facebook.friends.areFriends([friends[0]['uid']], [friends[1]['uid']])
1282 photos = facebook.photos.getAlbums(facebook.uid)