\n')
10 | sys.exit(1)
11 |
12 | if len(sys.argv) not in (2, 3):
13 | usage()
14 |
15 | if sys.argv[1] != 'startapp':
16 | usage()
17 |
18 | app_name = len(sys.argv) == 3 and sys.argv[2] or 'fbapp'
19 |
20 | try:
21 | sys.path.insert(0, os.getcwd())
22 | # Assumed to be in the same directory or current directory.
23 | import settings
24 | except ImportError:
25 | sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r "
26 | "or in the current directory. It appears you've customized things.\n"
27 | "You'll have to run django-admin.py, passing it your settings module.\n"
28 | "(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n"
29 | % __file__)
30 | sys.exit(1)
31 |
32 | from django.core import management
33 |
34 | directory = management.setup_environ(settings)
35 |
36 | if hasattr(management, 'color'):
37 | # Current svn version of django
38 | from django.core.management.color import color_style
39 | style = color_style()
40 | else:
41 | # Compatibility with 0.96
42 | from django.core.management import style
43 |
44 | project_dir = os.path.normpath(os.path.join(directory, '..'))
45 | parent_dir = os.path.basename(project_dir)
46 | project_name = os.path.basename(directory)
47 | if app_name == project_name:
48 | sys.stderr.write(
49 | style.ERROR(
50 | 'Error: You cannot create an app with the same name (%r) as your project.\n' %
51 | app_name))
52 | sys.exit(1)
53 | if app_name == 'facebook':
54 | sys.stderr.write(style.ERROR(
55 | 'Error: You cannot name your app "facebook", '
56 | 'since this can cause conflicts with imports in Python < 2.5.\n'))
57 | sys.exit(1)
58 | if not re.search(r'^\w+$', app_name):
59 | sys.stderr.write(
60 | style.ERROR(
61 | 'Error: %r is not a valid app name. Please use only numbers, letters and underscores.\n' %
62 | app_name))
63 | sys.exit(1)
64 |
65 | top_dir = os.path.join(directory, app_name)
66 | try:
67 | os.mkdir(top_dir)
68 | except OSError as e:
69 | sys.stderr.write(style.ERROR("Error: %s\n" % e))
70 | sys.exit(1)
71 |
72 | import facebook
73 |
74 | template_dir = os.path.join(
75 | facebook.__path__[0],
76 | 'djangofb',
77 | 'default_app')
78 |
79 | sys.stderr.write('Creating Facebook application %r...\n' % app_name)
80 |
81 | for d, subdirs, files in os.walk(template_dir):
82 | relative_dir = d[len(template_dir) + 1:]
83 | if relative_dir:
84 | os.mkdir(os.path.join(top_dir, relative_dir))
85 | subdirs[:] = [s for s in subdirs if not s.startswith('.')]
86 | for f in files:
87 | if f.endswith('.pyc'):
88 | continue
89 | path_old = os.path.join(d, f)
90 | path_new = os.path.join(top_dir, relative_dir, f)
91 | f_old = open(path_old, 'r')
92 | f_new = open(path_new, 'w')
93 | sys.stderr.write('Writing %s...\n' % path_new)
94 | f_new.write(
95 | f_old.read().replace(
96 | '{{ project }}',
97 | project_name).replace(
98 | '{{ app }}',
99 | app_name))
100 | f_new.close()
101 | f_old.close()
102 |
103 | sys.stderr.write('Done!\n\n')
104 |
105 | from django.conf import settings
106 |
107 | need_api_key = not hasattr(settings, 'FACEBOOK_API_KEY')
108 | need_middleware = 'facebook.djangofb.FacebookMiddleware' not in settings.MIDDLEWARE_CLASSES
109 | need_loader = 'django.template.loaders.app_directories.load_template_source' not in settings.TEMPLATE_LOADERS
110 | need_install_app = not '%s.%s' % (
111 | project_name, app_name) in settings.INSTALLED_APPS
112 |
113 | if need_api_key or need_middleware or need_loader or need_install_app:
114 | sys.stderr.write(
115 | """There are a couple of things you NEED to do before you can use this app:\n\n""")
116 | if need_api_key:
117 | sys.stderr.write(
118 | """ * Set FACEBOOK_API_KEY and FACEBOOK_SECRET_KEY to the appropriate values in settings.py\n\n""")
119 | if need_middleware:
120 | sys.stderr.write(
121 | """ * Add 'facebook.djangofb.FacebookMiddleware' to your MIDDLEWARE_CLASSES in settings.py\n\n""")
122 | if need_loader:
123 | sys.stderr.write(
124 | """ * Add 'django.template.loaders.app_directories.load_template_source'
125 | to your TEMPLATE_LOADERS in settings.py\n\n""")
126 | if need_install_app:
127 | sys.stderr.write(
128 | """ * Add '%s.%s' to your INSTALLED_APPS in settings.py\n\n""" %
129 | (project_name, app_name))
130 |
131 | sys.stderr.write(
132 | """The final step is to add (r'^%s/', include('%s.%s.urls')) to your urls.py, and then set your callback page in the application settings on Facebook to 'http://your.domain.com/%s/'.
133 |
134 | Good luck!
135 |
136 | """ %
137 | (project_name, project_name, app_name, project_name))
138 |
--------------------------------------------------------------------------------
/examples/fbsample/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | fbsample
4 |
5 |
6 |
7 |
8 |
9 | org.python.pydev.PyDevBuilder
10 |
11 |
12 |
13 |
14 |
15 | org.python.pydev.pythonNature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/examples/fbsample/.pydevproject:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | python 2.6
6 | Default
7 |
8 |
--------------------------------------------------------------------------------
/examples/fbsample/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sciyoshi/pyfacebook/ec2bbfb0c687f2775ae89e1c5201688ed6e69b8e/examples/fbsample/__init__.py
--------------------------------------------------------------------------------
/examples/fbsample/db.sqlite3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sciyoshi/pyfacebook/ec2bbfb0c687f2775ae89e1c5201688ed6e69b8e/examples/fbsample/db.sqlite3
--------------------------------------------------------------------------------
/examples/fbsample/fbapp/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sciyoshi/pyfacebook/ec2bbfb0c687f2775ae89e1c5201688ed6e69b8e/examples/fbsample/fbapp/__init__.py
--------------------------------------------------------------------------------
/examples/fbsample/fbapp/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 | # get_facebook_client lets us get the current Facebook object
4 | # from outside of a view, which lets us have cleaner code
5 | from facebook.djangofb import get_facebook_client
6 |
7 |
8 | def _2int(d, k):
9 | try:
10 | d = d.__dict__
11 | except:
12 | pass
13 |
14 | t = d.get(k, '')
15 | if t == 'None':
16 | t = 0
17 | else:
18 | t = int(t)
19 | return t
20 |
21 |
22 | class UserManager(models.Manager):
23 | """Custom manager for a Facebook User."""
24 |
25 | def get_current(self):
26 | """Gets a User object for the logged-in Facebook user."""
27 | facebook = get_facebook_client()
28 | user, created = self.get_or_create(id=_2int(facebook, 'uid'))
29 | if created:
30 | # we could do some custom actions for new users here...
31 | pass
32 | return user
33 |
34 |
35 | class User(models.Model):
36 | """A simple User model for Facebook users."""
37 |
38 | # We use the user's UID as the primary key in our database.
39 | id = models.IntegerField(primary_key=True)
40 |
41 | # TODO: The data that you want to store for each user would go here.
42 | # For this sample, we let users let people know their favorite progamming
43 | # language, in the spirit of Extended Info.
44 | language = models.CharField(max_length=64, default='Python')
45 |
46 | # Add the custom manager
47 | objects = UserManager()
48 |
--------------------------------------------------------------------------------
/examples/fbsample/fbapp/templates/canvas.fbml:
--------------------------------------------------------------------------------
1 |
2 | {% comment %}
3 | We can use {{ fbuser }} to get at the current user.
4 | {{ fbuser.id }} will be the user's UID, and {{ fbuser.language }}
5 | is his/her favorite language (Python :-).
6 | {% endcomment %}
7 | Welcome, !
8 |
9 |
10 |
11 | Your favorite language is {{ fbuser.language|escape }}.
12 |
13 |
14 |
22 |
23 |
--------------------------------------------------------------------------------
/examples/fbsample/fbapp/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls.defaults import *
2 |
3 | urlpatterns = patterns('fbsample.fbapp.views',
4 | (r'^$', 'canvas'),
5 | # Define other pages you want to create here
6 | )
7 |
--------------------------------------------------------------------------------
/examples/fbsample/fbapp/views.py:
--------------------------------------------------------------------------------
1 | from django.http import HttpResponse
2 | from django.views.generic.simple import direct_to_template
3 | # uncomment the following two lines and the one below
4 | # if you don't want to use a decorator instead of the middleware
5 | # from django.utils.decorators import decorator_from_middleware
6 | # from facebook.djangofb import FacebookMiddleware
7 |
8 | # Import the Django helpers
9 | import facebook.djangofb as facebook
10 |
11 | # The User model defined in models.py
12 | from models import User
13 |
14 | # We'll require login for our canvas page. This
15 | # isn't necessarily a good idea, as we might want
16 | # to let users see the page without granting our app
17 | # access to their info. See the wiki for details on how
18 | # to do this.
19 | # @decorator_from_middleware(FacebookMiddleware)
20 |
21 |
22 | @facebook.require_login()
23 | def canvas(request):
24 | # Get the User object for the currently logged in user
25 | user = User.objects.get_current()
26 |
27 | # Check if we were POSTed the user's new language of choice
28 | if 'language' in request.POST:
29 | user.language = request.POST['language'][:64]
30 | user.save()
31 |
32 | # User is guaranteed to be logged in, so pass canvas.fbml
33 | # an extra 'fbuser' parameter that is the User object for
34 | # the currently logged in user.
35 | return direct_to_template(
36 | request,
37 | 'canvas.fbml',
38 | extra_context={
39 | 'fbuser': user})
40 |
41 |
42 | @facebook.require_login()
43 | def ajax(request):
44 | return HttpResponse('hello world')
45 |
--------------------------------------------------------------------------------
/examples/fbsample/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from django.core.management import execute_manager
3 | try:
4 | import settings # Assumed to be in the same directory.
5 | except ImportError:
6 | import sys
7 | sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. "
8 | "It appears you've customized things.\nYou'll have to run django-admin.py, "
9 | "passing it your settings module.\n(If the file settings.py does indeed exist, "
10 | "it's causing an ImportError somehow.)\n" % __file__)
11 | sys.exit(1)
12 |
13 | if __name__ == "__main__":
14 | execute_manager(settings)
15 |
--------------------------------------------------------------------------------
/examples/fbsample/run.bat:
--------------------------------------------------------------------------------
1 | python manage.py runserver 0.0.0.0:80
--------------------------------------------------------------------------------
/examples/fbsample/settings.py:
--------------------------------------------------------------------------------
1 | # Django settings for fbsample project.
2 |
3 | DEBUG = True
4 | TEMPLATE_DEBUG = DEBUG
5 |
6 | ADMINS = (
7 | # ('Your Name', 'your_email@domain.com'),
8 | )
9 |
10 | MANAGERS = ADMINS
11 |
12 | # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
13 | DATABASE_ENGINE = 'sqlite3'
14 | # Or path to database file if using sqlite3.
15 | DATABASE_NAME = 'db.sqlite3'
16 | DATABASE_USER = '' # Not used with sqlite3.
17 | DATABASE_PASSWORD = '' # Not used with sqlite3.
18 | # Set to empty string for localhost. Not used with sqlite3.
19 | DATABASE_HOST = ''
20 | # Set to empty string for default. Not used with sqlite3.
21 | DATABASE_PORT = ''
22 |
23 | # Local time zone for this installation. Choices can be found here:
24 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
25 | # although not all choices may be available on all operating systems.
26 | # If running in a Windows environment this must be set to the same as your
27 | # system time zone.
28 | TIME_ZONE = 'America/Chicago'
29 |
30 | # Language code for this installation. All choices can be found here:
31 | # http://www.i18nguy.com/unicode/language-identifiers.html
32 | LANGUAGE_CODE = 'en-us'
33 |
34 | SITE_ID = 1
35 |
36 | # If you set this to False, Django will make some optimizations so as not
37 | # to load the internationalization machinery.
38 | USE_I18N = True
39 |
40 | # Absolute path to the directory that holds media.
41 | # Example: "/home/media/media.lawrence.com/"
42 | MEDIA_ROOT = ''
43 |
44 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a
45 | # trailing slash if there is a path component (optional in other cases).
46 | # Examples: "http://media.lawrence.com", "http://example.com/media/"
47 | MEDIA_URL = ''
48 |
49 | # URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
50 | # trailing slash.
51 | # Examples: "http://foo.com/media/", "/media/".
52 | ADMIN_MEDIA_PREFIX = '/media/'
53 |
54 | # Make this unique, and don't share it with anybody.
55 | SECRET_KEY = 'yg6zh@+u^w3agtjwy^da)#277d3j#a%3m@)pev8_j0ozztwe4+'
56 |
57 | # List of callables that know how to import templates from various sources.
58 | TEMPLATE_LOADERS = (
59 | 'django.template.loaders.filesystem.load_template_source',
60 | 'django.template.loaders.app_directories.load_template_source',
61 | # 'django.template.loaders.eggs.load_template_source',
62 | )
63 |
64 | MIDDLEWARE_CLASSES = (
65 | 'django.middleware.common.CommonMiddleware',
66 | 'django.contrib.sessions.middleware.SessionMiddleware',
67 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
68 |
69 | 'facebook.djangofb.FacebookMiddleware',
70 | )
71 |
72 | ROOT_URLCONF = 'fbsample.urls'
73 |
74 | TEMPLATE_DIRS = (
75 | # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
76 | # Always use forward slashes, even on Windows.
77 | # Don't forget to use absolute paths, not relative paths.
78 | )
79 |
80 | INSTALLED_APPS = (
81 | 'django.contrib.auth',
82 | 'django.contrib.contenttypes',
83 | 'django.contrib.sessions',
84 | 'django.contrib.sites',
85 |
86 | 'fbsample.fbapp',
87 | )
88 |
89 | # get it from here
90 | # http://www.facebook.com/editapps.php?ref=mb
91 | FACEBOOK_API_KEY = 'x'
92 | FACEBOOK_SECRET_KEY = 'xx'
93 |
--------------------------------------------------------------------------------
/examples/fbsample/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls.defaults import *
2 |
3 | # Uncomment the next two lines to enable the admin:
4 | # from django.contrib import admin
5 | # admin.autodiscover()
6 |
7 | urlpatterns = patterns('',
8 | # Example:
9 | # (r'^fbsample/', include('fbsample.foo.urls')),
10 |
11 | # Uncomment the admin/doc line below and add 'django.contrib.admindocs'
12 | # to INSTALLED_APPS to enable admin documentation:
13 | # (r'^admin/doc/', include('django.contrib.admindocs.urls')),
14 |
15 | # Uncomment the next line to enable the admin:
16 | # (r'^admin/', include(admin.site.urls)),
17 |
18 | (r'^fbsample/', include('fbsample.fbapp.urls')),
19 | )
20 |
--------------------------------------------------------------------------------
/facebook/__init__.py:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env python
2 | #
3 | # pyfacebook - Python bindings for the Facebook API
4 | #
5 | # Copyright (c) 2008, Samuel Cormier-Iijima
6 | # All rights reserved.
7 | #
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.
18 | #
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.
29 |
30 | """
31 | Python bindings for the Facebook API (pyfacebook - http://code.google.com/p/pyfacebook)
32 |
33 | PyFacebook is a client library that wraps the Facebook API.
34 |
35 | For more information, see
36 |
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
40 |
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.
45 | """
46 |
47 | import sys
48 | import time
49 | import struct
50 | import urllib
51 | import urllib2
52 | import httplib
53 | import hmac
54 | try:
55 | import hashlib
56 | except ImportError:
57 | import md5 as hashlib
58 | import binascii
59 | import urlparse
60 | import mimetypes
61 |
62 | # try to use simplejson first, otherwise fallback to XML
63 | RESPONSE_FORMAT = 'JSON'
64 | try:
65 | import json as simplejson
66 | simplejson.loads
67 | except (ImportError, AttributeError):
68 | try:
69 | import simplejson
70 | simplejson.loads
71 | except (ImportError, AttributeError):
72 | try:
73 | from django.utils import simplejson
74 | simplejson.loads
75 | except (ImportError, AttributeError):
76 | try:
77 | import jsonlib as simplejson
78 | simplejson.loads
79 | except (ImportError, AttributeError):
80 | from xml.dom import minidom
81 | RESPONSE_FORMAT = 'XML'
82 |
83 | # support Google App Engine. GAE does not have a working urllib.urlopen.
84 | try:
85 | from google.appengine.api import urlfetch
86 |
87 | def urlread(url, data=None, headers=None):
88 | if data is not None:
89 | if headers is None:
90 | headers = {"Content-type": "application/x-www-form-urlencoded"}
91 | method = urlfetch.POST
92 | else:
93 | if headers is None:
94 | headers = {}
95 | method = urlfetch.GET
96 |
97 | result = urlfetch.fetch(url, method=method,
98 | payload=data, headers=headers)
99 |
100 | if result.status_code == 200:
101 | return result.content
102 | else:
103 | raise urllib2.URLError(
104 | "fetch error url=%s, code=%d" %
105 | (url, result.status_code))
106 |
107 | except ImportError:
108 | def urlread(url, data=None):
109 | res = urllib2.urlopen(url, data=data)
110 | return res.read()
111 |
112 | __all__ = ['Facebook', 'create_hmac']
113 |
114 | VERSION = '1.0a2'
115 |
116 | FACEBOOK_URL = 'http://api.facebook.com/restserver.php'
117 | FACEBOOK_VIDEO_URL = 'http://api-video.facebook.com/restserver.php'
118 | FACEBOOK_SECURE_URL = 'https://api.facebook.com/restserver.php'
119 |
120 |
121 | def create_hmac(key, tbhashed):
122 | return hmac.new(key, tbhashed, hashlib.sha1).hexdigest()
123 |
124 |
125 | class json(object):
126 | pass
127 |
128 | # simple IDL for the Facebook API
129 | METHODS = {
130 | # dashboard methods
131 | 'dashboard': {
132 | 'incrementCount': [
133 | ('uid', int, ['optional'])
134 | ],
135 | 'decrementCount': [
136 | ('uid', int, ['optional'])
137 | ],
138 | 'getCount': [
139 | ('uid', int, ['optional'])
140 | ],
141 | 'setCount': [
142 | ('count', int, []),
143 | ('uid', int, ['optional'])
144 | ],
145 | 'getActivity': [
146 | ('activity_ids', list, ['optional']),
147 | ('uid', int, ['optional'])
148 | ],
149 | 'publishActivity': [
150 | ('activity', json, []),
151 |
152 | ]
153 |
154 | },
155 | 'application': {
156 | 'getPublicInfo': [
157 | ('application_id', int, ['optional']),
158 | ('application_api_key', str, ['optional']),
159 | ('application_canvas_name', str, ['optional']),
160 | ],
161 | },
162 |
163 | # admin methods
164 | 'admin': {
165 | 'getAllocation': [
166 | ('integration_point_name', str, []),
167 | ],
168 | 'getRestrictionInfo': [],
169 | 'setRestrictionInfo': [
170 | ('format', str, ['optional']),
171 | ('callback', str, ['optional']),
172 | ('restriction_str', json, ['optional']),
173 | ],
174 | # Some methods don't work with access_tokens, the signed option forces
175 | # use of the secret_key signature (avoids error 15 and, sometimes, 8)
176 | 'getAppProperties': [
177 | ('properties', list, []),
178 | 'signed'
179 | ],
180 | 'setAppProperties': [
181 | ('properties', json, []),
182 | 'signed'
183 | ],
184 | },
185 |
186 | # auth methods
187 | 'auth': {
188 | 'revokeAuthorization': [
189 | ('uid', int, ['optional']),
190 | ],
191 | 'revokeExtendedPermission': [
192 | ('perm', str, []),
193 | ('uid', int, ['optional']),
194 | ],
195 | },
196 |
197 | # feed methods
198 | 'feed': {
199 | 'publishStoryToUser': [
200 | ('title', str, []),
201 | ('body', str, ['optional']),
202 | ('image_1', str, ['optional']),
203 | ('image_1_link', str, ['optional']),
204 | ('image_2', str, ['optional']),
205 | ('image_2_link', str, ['optional']),
206 | ('image_3', str, ['optional']),
207 | ('image_3_link', str, ['optional']),
208 | ('image_4', str, ['optional']),
209 | ('image_4_link', str, ['optional']),
210 | ('priority', int, ['optional']),
211 | ],
212 |
213 | 'publishActionOfUser': [
214 | ('title', str, []),
215 | ('body', str, ['optional']),
216 | ('image_1', str, ['optional']),
217 | ('image_1_link', str, ['optional']),
218 | ('image_2', str, ['optional']),
219 | ('image_2_link', str, ['optional']),
220 | ('image_3', str, ['optional']),
221 | ('image_3_link', str, ['optional']),
222 | ('image_4', str, ['optional']),
223 | ('image_4_link', str, ['optional']),
224 | ('priority', int, ['optional']),
225 | ],
226 |
227 | 'publishTemplatizedAction': [
228 | ('title_template', str, []),
229 | ('page_actor_id', int, ['optional']),
230 | ('title_data', json, ['optional']),
231 | ('body_template', str, ['optional']),
232 | ('body_data', json, ['optional']),
233 | ('body_general', str, ['optional']),
234 | ('image_1', str, ['optional']),
235 | ('image_1_link', str, ['optional']),
236 | ('image_2', str, ['optional']),
237 | ('image_2_link', str, ['optional']),
238 | ('image_3', str, ['optional']),
239 | ('image_3_link', str, ['optional']),
240 | ('image_4', str, ['optional']),
241 | ('image_4_link', str, ['optional']),
242 | ('target_ids', list, ['optional']),
243 | ],
244 |
245 | 'registerTemplateBundle': [
246 | ('one_line_story_templates', json, []),
247 | ('short_story_templates', json, ['optional']),
248 | ('full_story_template', json, ['optional']),
249 | ('action_links', json, ['optional']),
250 | ],
251 |
252 | 'deactivateTemplateBundleByID': [
253 | ('template_bundle_id', int, []),
254 | ],
255 |
256 | 'getRegisteredTemplateBundles': [],
257 |
258 | 'getRegisteredTemplateBundleByID': [
259 | ('template_bundle_id', str, []),
260 | ],
261 |
262 | 'publishUserAction': [
263 | ('template_bundle_id', int, []),
264 | ('template_data', json, ['optional']),
265 | ('target_ids', list, ['optional']),
266 | ('body_general', str, ['optional']),
267 | ('story_size', int, ['optional']),
268 | ],
269 | },
270 |
271 | # fql methods
272 | 'fql': {
273 | 'query': [
274 | ('query', str, []),
275 | ],
276 | 'multiquery': [
277 | ('queries', json, []),
278 | ],
279 | },
280 |
281 | # friends methods
282 | 'friends': {
283 | 'areFriends': [
284 | ('uids1', list, []),
285 | ('uids2', list, []),
286 | ],
287 |
288 | 'get': [
289 | ('flid', int, ['optional']),
290 | ],
291 |
292 | 'getLists': [],
293 |
294 | 'getAppUsers': [],
295 |
296 | 'getMutualFriends': [
297 | ('target_uid', int, []),
298 | ('source_uid', int, ['optional']),
299 | ],
300 | },
301 |
302 | # notifications methods
303 | 'notifications': {
304 | 'get': [],
305 |
306 | 'send': [
307 | ('to_ids', list, []),
308 | ('notification', str, []),
309 | ('email', str, ['optional']),
310 | ('type', str, ['optional']),
311 | ],
312 |
313 | 'sendRequest': [
314 | ('to_ids', list, []),
315 | ('type', str, []),
316 | ('content', str, []),
317 | ('image', str, []),
318 | ('invite', bool, []),
319 | ],
320 |
321 | 'sendEmail': [
322 | ('recipients', list, []),
323 | ('subject', str, []),
324 | ('text', str, ['optional']),
325 | ('fbml', str, ['optional']),
326 | ]
327 | },
328 |
329 | # profile methods
330 | 'profile': {
331 | 'setFBML': [
332 | ('markup', str, ['optional']),
333 | ('uid', int, ['optional']),
334 | ('profile', str, ['optional']),
335 | ('profile_action', str, ['optional']),
336 | ('mobile_fbml', str, ['optional']),
337 | ('profile_main', str, ['optional']),
338 | ],
339 |
340 | 'getFBML': [
341 | ('uid', int, ['optional']),
342 | ('type', int, ['optional']),
343 | ],
344 |
345 | 'setInfo': [
346 | ('title', str, []),
347 | ('type', int, []),
348 | ('info_fields', json, []),
349 | ('uid', int, []),
350 | ],
351 |
352 | 'getInfo': [
353 | ('uid', int, []),
354 | ],
355 |
356 | 'setInfoOptions': [
357 | ('field', str, []),
358 | ('options', json, []),
359 | ],
360 |
361 | 'getInfoOptions': [
362 | ('field', str, []),
363 | ],
364 | },
365 |
366 | # users methods
367 | 'users': {
368 | 'getInfo': [
369 | ('uids', list, []),
370 | ('fields', list, [('default', ['name'])]),
371 | ],
372 |
373 | 'getStandardInfo': [
374 | ('uids', list, []),
375 | ('fields', list, [('default', ['uid'])]),
376 | ],
377 |
378 | 'getLoggedInUser': [],
379 |
380 | 'isAppAdded': [],
381 |
382 | 'isAppUser': [
383 | ('uid', int, []),
384 | ],
385 |
386 | 'hasAppPermission': [
387 | ('ext_perm', str, []),
388 | ('uid', int, ['optional']),
389 | ],
390 |
391 | 'setStatus': [
392 | ('status', str, []),
393 | ('clear', bool, []),
394 | ('status_includes_verb', bool, ['optional']),
395 | ('uid', int, ['optional']),
396 | ],
397 | },
398 |
399 | # events methods
400 | 'events': {
401 | 'cancel': [
402 | ('eid', int, []),
403 | ('cancel_message', str, ['optional']),
404 | ],
405 |
406 | 'create': [
407 | ('event_info', json, []),
408 | ],
409 |
410 | 'edit': [
411 | ('eid', int, []),
412 | ('event_info', json, []),
413 | ],
414 |
415 | 'get': [
416 | ('uid', int, ['optional']),
417 | ('eids', list, ['optional']),
418 | ('start_time', int, ['optional']),
419 | ('end_time', int, ['optional']),
420 | ('rsvp_status', str, ['optional']),
421 | ],
422 |
423 | 'getMembers': [
424 | ('eid', int, []),
425 | ],
426 |
427 | 'invite': [
428 | ('eid', int, []),
429 | ('uids', list, []),
430 | ('personal_message', str, ['optional']),
431 | ],
432 |
433 | 'rsvp': [
434 | ('eid', int, []),
435 | ('rsvp_status', str, []),
436 | ],
437 |
438 | 'edit': [
439 | ('eid', int, []),
440 | ('event_info', json, []),
441 | ],
442 |
443 | 'invite': [
444 | ('eid', int, []),
445 | ('uids', list, []),
446 | ('personal_message', str, ['optional']),
447 | ],
448 | },
449 |
450 | # update methods
451 | 'update': {
452 | 'decodeIDs': [
453 | ('ids', list, []),
454 | ],
455 | },
456 |
457 | # groups methods
458 | 'groups': {
459 | 'get': [
460 | ('uid', int, ['optional']),
461 | ('gids', list, ['optional']),
462 | ],
463 |
464 | 'getMembers': [
465 | ('gid', int, []),
466 | ],
467 | },
468 |
469 | # marketplace methods
470 | 'marketplace': {
471 | 'createListing': [
472 | ('listing_id', int, []),
473 | ('show_on_profile', bool, []),
474 | ('listing_attrs', str, []),
475 | ],
476 |
477 | 'getCategories': [],
478 |
479 | 'getListings': [
480 | ('listing_ids', list, []),
481 | ('uids', list, []),
482 | ],
483 |
484 | 'getSubCategories': [
485 | ('category', str, []),
486 | ],
487 |
488 | 'removeListing': [
489 | ('listing_id', int, []),
490 | ('status', str, []),
491 | ],
492 |
493 | 'search': [
494 | ('category', str, ['optional']),
495 | ('subcategory', str, ['optional']),
496 | ('query', str, ['optional']),
497 | ],
498 | },
499 |
500 | # pages methods
501 | 'pages': {
502 | 'getInfo': [
503 | ('fields', list, [('default', ['page_id', 'name'])]),
504 | ('page_ids', list, ['optional']),
505 | ('uid', int, ['optional']),
506 | ],
507 |
508 | 'isAdmin': [
509 | ('page_id', int, []),
510 | ],
511 |
512 | 'isAppAdded': [
513 | ('page_id', int, []),
514 | ],
515 |
516 | 'isFan': [
517 | ('page_id', int, []),
518 | ('uid', int, []),
519 | ],
520 | },
521 |
522 | # photos methods
523 | 'photos': {
524 | 'addTag': [
525 | ('pid', int, []),
526 | ('tag_uid', int, [('default', 0)]),
527 | ('tag_text', str, [('default', '')]),
528 | ('x', float, [('default', 50)]),
529 | ('y', float, [('default', 50)]),
530 | ('tags', json, ['optional']),
531 | ],
532 |
533 | 'createAlbum': [
534 | ('name', str, []),
535 | ('location', str, ['optional']),
536 | ('description', str, ['optional']),
537 | ],
538 |
539 | 'get': [
540 | ('subj_id', int, ['optional']),
541 | ('aid', int, ['optional']),
542 | ('pids', list, ['optional']),
543 | ],
544 |
545 | 'getAlbums': [
546 | ('uid', int, ['optional']),
547 | ('aids', list, ['optional']),
548 | ],
549 |
550 | 'getTags': [
551 | ('pids', list, []),
552 | ],
553 | },
554 |
555 | # videos methods
556 | 'video': {
557 | 'getUploadLimits': [
558 | ],
559 | },
560 |
561 | # status methods
562 | 'status': {
563 | 'get': [
564 | ('uid', int, ['optional']),
565 | ('limit', int, ['optional']),
566 | ],
567 | 'set': [
568 | ('status', str, ['optional']),
569 | ('uid', int, ['optional']),
570 | ],
571 | },
572 |
573 | # fbml methods
574 | 'fbml': {
575 | 'refreshImgSrc': [
576 | ('url', str, []),
577 | ],
578 |
579 | 'refreshRefUrl': [
580 | ('url', str, []),
581 | ],
582 |
583 | 'setRefHandle': [
584 | ('handle', str, []),
585 | ('fbml', str, []),
586 | ],
587 | },
588 |
589 | # SMS Methods
590 | 'sms': {
591 | 'canSend': [
592 | ('uid', int, []),
593 | ],
594 |
595 | 'send': [
596 | ('uid', int, []),
597 | ('message', str, []),
598 | ('session_id', int, []),
599 | ('req_session', bool, []),
600 | ],
601 | },
602 |
603 | 'data': {
604 | 'getCookies': [
605 | ('uid', int, []),
606 | ('string', str, ['optional']),
607 | ],
608 |
609 | 'setCookie': [
610 | ('uid', int, []),
611 | ('name', str, []),
612 | ('value', str, []),
613 | ('expires', int, ['optional']),
614 | ('path', str, ['optional']),
615 | ],
616 | },
617 |
618 | # connect methods
619 | 'connect': {
620 | 'registerUsers': [
621 | ('accounts', json, []),
622 | ],
623 |
624 | 'unregisterUsers': [
625 | ('email_hashes', json, []),
626 | ],
627 |
628 | 'getUnconnectedFriendsCount': [
629 | ],
630 | },
631 |
632 | 'links': {
633 | 'post': [
634 | ('url', str, []),
635 | ('comment', str, []),
636 | ('uid', int, []),
637 | ('image', str, ['optional']),
638 | ('callback', str, ['optional']),
639 | ],
640 | 'preview': [
641 | ('url', str, []),
642 | ('callback', str, ['optional']),
643 | ],
644 | },
645 |
646 | # stream methods (beta)
647 | 'stream': {
648 | 'addComment': [
649 | ('post_id', int, []),
650 | ('comment', str, []),
651 | ('uid', int, ['optional']),
652 | ],
653 |
654 | 'addLike': [
655 | ('uid', int, ['optional']),
656 | ('post_id', int, ['optional']),
657 | ],
658 |
659 | 'get': [
660 | ('viewer_id', int, ['optional']),
661 | ('source_ids', list, ['optional']),
662 | ('start_time', int, ['optional']),
663 | ('end_time', int, ['optional']),
664 | ('limit', int, ['optional']),
665 | ('filter_key', str, ['optional']),
666 | ],
667 |
668 | 'getComments': [
669 | ('post_id', int, []),
670 | ],
671 |
672 | 'getFilters': [
673 | ('uid', int, ['optional']),
674 | ],
675 |
676 | 'publish': [
677 | ('message', str, ['optional']),
678 | ('attachment', json, ['optional']),
679 | ('action_links', json, ['optional']),
680 | ('target_id', str, ['optional']),
681 | ('uid', str, ['optional']),
682 | ],
683 |
684 | 'remove': [
685 | ('post_id', int, []),
686 | ('uid', int, ['optional']),
687 | ],
688 |
689 | 'removeComment': [
690 | ('comment_id', int, []),
691 | ('uid', int, ['optional']),
692 | ],
693 |
694 | 'removeLike': [
695 | ('uid', int, ['optional']),
696 | ('post_id', int, ['optional']),
697 | ],
698 | },
699 |
700 | # livemessage methods (beta)
701 | 'livemessage': {
702 | 'send': [
703 | ('recipient', int, []),
704 | ('event_name', str, []),
705 | ('message', str, []),
706 | ],
707 | },
708 |
709 | # dashboard methods (beta)
710 | 'dashboard': {
711 | # setting counters for a single user
712 | 'decrementCount': [
713 | ('uid', int, []),
714 | ],
715 |
716 | 'incrementCount': [
717 | ('uid', int, []),
718 | ],
719 |
720 | 'getCount': [
721 | ('uid', int, []),
722 | ],
723 |
724 | 'setCount': [
725 | ('uid', int, []),
726 | ('count', int, []),
727 | ],
728 |
729 | # setting counters for multiple users
730 | 'multiDecrementCount': [
731 | ('uids', list, []),
732 | ],
733 |
734 | 'multiIncrementCount': [
735 | ('uids', list, []),
736 | ],
737 |
738 | 'multiGetCount': [
739 | ('uids', list, []),
740 | ],
741 |
742 | 'multiSetCount': [
743 | ('uids', list, []),
744 | ],
745 |
746 | # setting news for a single user
747 | 'addNews': [
748 | ('news', json, []),
749 | ('image', str, ['optional']),
750 | ('uid', int, ['optional']),
751 | ],
752 |
753 | 'getNews': [
754 | ('uid', int, []),
755 | ('news_ids', list, ['optional']),
756 | ],
757 |
758 | 'clearNews': [
759 | ('uid', int, []),
760 | ('news_ids', list, ['optional']),
761 | ],
762 |
763 | # setting news for multiple users
764 | 'multiAddNews': [
765 | ('uids', list, []),
766 | ('news', json, []),
767 | ('image', str, ['optional']),
768 | ],
769 |
770 | 'multiGetNews': [
771 | ('uids', json, []),
772 | ],
773 |
774 | 'multiClearNews': [
775 | ('uids', json, []),
776 | ],
777 |
778 | # setting application news for all users
779 | 'addGlobalNews': [
780 | ('news', json, []),
781 | ('image', str, ['optional']),
782 | ],
783 |
784 | 'getGlobalNews': [
785 | ('news_ids', list, ['optional']),
786 | ],
787 |
788 | 'clearGlobalNews': [
789 | ('news_ids', list, ['optional']),
790 | ],
791 |
792 | # user activity
793 | 'getActivity': [
794 | ('activity_ids', list, ['optional']),
795 | ],
796 |
797 | 'publishActivity': [
798 | ('activity', json, []),
799 | ],
800 |
801 | 'removeActivity': [
802 | ('activity_ids', list, []),
803 | ],
804 | },
805 |
806 | # comments methods (beta)
807 | 'comments': {
808 | 'add': [
809 | # text should be after xid/object_id, but is required
810 | ('text', str, []),
811 | # One of xid and object_is is required. Can this be expressed?
812 | ('xid', str, ['optional']),
813 | ('object_id', str, ['optional']),
814 | ('uid', int, ['optional']),
815 | ('title', str, ['optional']),
816 | ('url', str, ['optional']),
817 | ('publish_to_stream', bool, [('default', False)]),
818 | ],
819 |
820 | 'remove': [
821 | # One of xid and object_is is required. Can this be expressed?
822 | ('xid', str, ['optional']),
823 | ('object_id', str, ['optional']),
824 | # comment_id should be required
825 | ('comment_id', str, ['optional']),
826 | ],
827 |
828 | 'get': [
829 | # One of xid and object_is is required. Can this be expressed?
830 | ('xid', str, ['optional']),
831 | ('object_id', str, ['optional']),
832 | ],
833 | }
834 | }
835 |
836 |
837 | def __fixup_param(name, klass, options, param):
838 | optional = 'optional' in options
839 | default = [x[1] for x in options if isinstance(x, tuple) and x[
840 | 0] == 'default']
841 | if default:
842 | default = default[0]
843 | else:
844 | default = None
845 | if param is None:
846 | if klass is list and default:
847 | param = default[:]
848 | else:
849 | param = default
850 | if klass is json and isinstance(param, (list, dict)):
851 | param = simplejson.dumps(param)
852 | return param
853 |
854 |
855 | def __generate_facebook_method(namespace, method_name, param_data):
856 | # This method-level option forces the method to be signed rather than using
857 | # the access_token
858 | signed = False
859 | if 'signed' in param_data:
860 | signed = True
861 | param_data.remove('signed')
862 |
863 | # a required parameter doesn't have 'optional' in the options,
864 | # and has no tuple option that starts with 'default'
865 | required = [x for x in param_data if 'optional' not in x[2] and not [
866 | y for y in x[2] if isinstance(y, tuple) and y and y[0] == 'default']]
867 |
868 | def facebook_method(self, *args, **kwargs):
869 | params = {}
870 | for i, arg in enumerate(args):
871 | params[param_data[i][0]] = arg
872 | params.update(kwargs)
873 |
874 | for param in required:
875 | if param[0] not in params:
876 | raise TypeError("missing parameter %s" % param[0])
877 |
878 | for name, klass, options in param_data:
879 | if name in params:
880 | params[name] = __fixup_param(
881 | name, klass, options, params[name])
882 |
883 | return self(method_name, params, signed=signed)
884 |
885 | facebook_method.__name__ = method_name
886 | facebook_method.__doc__ = "Facebook API call. See http://developers.facebook.com/documentation.php?v=1.0&method=%s.%s" % (
887 | namespace, method_name)
888 |
889 | return facebook_method
890 |
891 |
892 | class Proxy(object):
893 | """Represents a "namespace" of Facebook API calls."""
894 |
895 | def __init__(self, client, name):
896 | self._client = client
897 | self._name = name
898 |
899 | def __call__(
900 | self,
901 | method=None,
902 | args=None,
903 | add_session_args=True,
904 | signed=False):
905 | # for Django templates
906 | if method is None:
907 | return self
908 |
909 | if add_session_args:
910 | self._client._add_session_args(args)
911 |
912 | return self._client(
913 | '%s.%s' %
914 | (self._name, method), args, signed=signed)
915 |
916 |
917 | # generate the Facebook proxies
918 | def __generate_proxies():
919 | for namespace in METHODS:
920 | methods = {}
921 |
922 | for method, param_data in METHODS[namespace].iteritems():
923 | methods[method] = __generate_facebook_method(
924 | namespace, method, param_data)
925 |
926 | proxy = type('%sProxy' % namespace.title(), (Proxy,), methods)
927 |
928 | globals()[proxy.__name__] = proxy
929 |
930 | __generate_proxies()
931 |
932 |
933 | class FacebookError(Exception):
934 | """Exception class for errors received from Facebook."""
935 |
936 | def __init__(self, code, msg, args=None):
937 | self.code = code
938 | self.msg = msg
939 | self.extra_args = args
940 | Exception.__init__(self, code, msg, args)
941 |
942 | def __str__(self):
943 | return 'Error %s: %s' % (self.code, self.msg)
944 |
945 |
946 | class AuthProxy(AuthProxy):
947 | """Special proxy for facebook.auth."""
948 |
949 | def getSession(self):
950 | """Facebook API call. See http://developers.facebook.com/documentation.php?v=1.0&method=auth.getSession"""
951 | args = {}
952 | try:
953 | args['auth_token'] = self._client.auth_token
954 | except AttributeError:
955 | raise RuntimeError('Client does not have auth_token set.')
956 | try:
957 | args['generate_session_secret'] = self._client.generate_session_secret
958 | except AttributeError:
959 | pass
960 | result = self._client('%s.getSession' % self._name, args)
961 | self._client.session_key = result['session_key']
962 | self._client.uid = result['uid']
963 | self._client.secret = result.get('secret')
964 | self._client.session_key_expires = result['expires']
965 | return result
966 |
967 | def createToken(self):
968 | """Facebook API call. See http://developers.facebook.com/documentation.php?v=1.0&method=auth.createToken"""
969 | token = self._client('%s.createToken' % self._name)
970 | self._client.auth_token = token
971 | return token
972 |
973 |
974 | class FriendsProxy(FriendsProxy):
975 | """Special proxy for facebook.friends."""
976 |
977 | def get(self, **kwargs):
978 | """Facebook API call. See http://developers.facebook.com/documentation.php?v=1.0&method=friends.get"""
979 | if not kwargs.get('flid') and self._client._friends:
980 | return self._client._friends
981 | return super(FriendsProxy, self).get(**kwargs)
982 |
983 |
984 | class PhotosProxy(PhotosProxy):
985 | """Special proxy for facebook.photos."""
986 |
987 | def upload(
988 | self,
989 | image,
990 | aid=None,
991 | uid=None,
992 | caption=None,
993 | size=(
994 | 720,
995 | 720),
996 | filename=None,
997 | callback=None):
998 | """Facebook API call. See http://developers.facebook.com/documentation.php?v=1.0&method=photos.upload
999 |
1000 | size -- an optional size (width, height) to resize the image to before uploading. Resizes by default
1001 | to Facebook's maximum display dimension of 720.
1002 | """
1003 | args = {}
1004 |
1005 | if uid is not None:
1006 | args['uid'] = uid
1007 |
1008 | if aid is not None:
1009 | args['aid'] = aid
1010 |
1011 | if caption is not None:
1012 | args['caption'] = caption
1013 |
1014 | if self._client.oauth2:
1015 | url = self._client.facebook_secure_url
1016 | else:
1017 | url = self._client.facebook_url
1018 |
1019 | args = self._client._build_post_args(
1020 | 'facebook.photos.upload',
1021 | self._client._add_session_args(args))
1022 |
1023 | try:
1024 | import cStringIO as StringIO
1025 | except ImportError:
1026 | import StringIO
1027 |
1028 | # check for a filename specified...if the user is passing binary data in
1029 | # image then a filename will be specified
1030 | if filename is None:
1031 | try:
1032 | import Image
1033 | except ImportError:
1034 | data = StringIO.StringIO(open(image, 'rb').read())
1035 | else:
1036 | img = Image.open(image)
1037 | if size:
1038 | img.thumbnail(size, Image.ANTIALIAS)
1039 | data = StringIO.StringIO()
1040 | img.save(data, img.format)
1041 | else:
1042 | # there was a filename specified, which indicates that image was not
1043 | # the path to an image file but rather the binary data of a file
1044 | data = StringIO.StringIO(image)
1045 | image = filename
1046 |
1047 | content_type, body = self.__encode_multipart_formdata(
1048 | list(args.iteritems()), [(image, data)])
1049 | urlinfo = urlparse.urlsplit(url)
1050 | try:
1051 | content_length = len(body)
1052 | chunk_size = 4096
1053 |
1054 | if self._client.oauth2:
1055 | h = httplib.HTTPSConnection(urlinfo[1])
1056 | else:
1057 | h = httplib.HTTPConnection(urlinfo[1])
1058 | h.putrequest('POST', urlinfo[2])
1059 | h.putheader('Content-Type', content_type)
1060 | h.putheader('Content-Length', str(content_length))
1061 | h.putheader('MIME-Version', '1.0')
1062 | h.putheader('User-Agent', 'PyFacebook Client Library')
1063 | h.endheaders()
1064 |
1065 | if callback:
1066 | count = 0
1067 | while len(body) > 0:
1068 | if len(body) < chunk_size:
1069 | data = body
1070 | body = ''
1071 | else:
1072 | data = body[0:chunk_size]
1073 | body = body[chunk_size:]
1074 |
1075 | h.send(data)
1076 | count += 1
1077 | callback(count, chunk_size, content_length)
1078 | else:
1079 | h.send(body)
1080 |
1081 | response = h.getresponse()
1082 |
1083 | if response.status != 200:
1084 | raise Exception(
1085 | 'Error uploading photo: Facebook returned HTTP %s (%s)' %
1086 | (response.status, response.reason))
1087 | response = response.read()
1088 | except:
1089 | # sending the photo failed, perhaps we are using GAE
1090 | try:
1091 | from google.appengine.api import urlfetch
1092 |
1093 | try:
1094 | response = urlread(
1095 | url=url,
1096 | data=body,
1097 | headers={
1098 | 'POST': urlinfo[2],
1099 | 'Content-Type': content_type,
1100 | 'MIME-Version': '1.0'})
1101 | except urllib2.URLError:
1102 | raise Exception(
1103 | 'Error uploading photo: Facebook returned %s' %
1104 | (response))
1105 | except ImportError:
1106 | # could not import from google.appengine.api, so we are not
1107 | # running in GAE
1108 | raise Exception('Error uploading photo.')
1109 |
1110 | return self._client._parse_response(response, 'facebook.photos.upload')
1111 |
1112 | def __encode_multipart_formdata(self, fields, files):
1113 | """Encodes a multipart/form-data message to upload an image."""
1114 | boundary = '-------tHISiStheMulTIFoRMbOUNDaRY'
1115 | crlf = '\r\n'
1116 | l = []
1117 |
1118 | for (key, value) in fields:
1119 | l.append('--' + boundary)
1120 | l.append('Content-Disposition: form-data; name="%s"' % str(key))
1121 | l.append('')
1122 | l.append(str(value))
1123 | for (filename, value) in files:
1124 | l.append('--' + boundary)
1125 | l.append(
1126 | 'Content-Disposition: form-data; filename="%s"' %
1127 | (str(filename), ))
1128 | l.append('Content-Type: %s' % self.__get_content_type(filename))
1129 | l.append('')
1130 | l.append(value.getvalue())
1131 | l.append('--' + boundary + '--')
1132 | l.append('')
1133 | body = crlf.join(l)
1134 | content_type = 'multipart/form-data; boundary=%s' % boundary
1135 | return content_type, body
1136 |
1137 | def __get_content_type(self, filename):
1138 | """Returns a guess at the MIME type of the file from the filename."""
1139 | return str(mimetypes.guess_type(filename)[
1140 | 0]) or 'application/octet-stream'
1141 |
1142 |
1143 | class VideoProxy(VideoProxy):
1144 | """Special proxy for facebook.video."""
1145 |
1146 | def upload(
1147 | self,
1148 | video,
1149 | aid=None,
1150 | title=None,
1151 | description=None,
1152 | filename=None,
1153 | uid=None,
1154 | privacy=None,
1155 | callback=None):
1156 | """Facebook API call. See http://wiki.developers.facebook.com/index.php/Video.upload"""
1157 | args = {}
1158 |
1159 | if aid is not None:
1160 | args['aid'] = aid
1161 |
1162 | if title is not None:
1163 | args['title'] = title
1164 |
1165 | if description is not None:
1166 | args['description'] = description
1167 |
1168 | if uid is not None:
1169 | args['uid'] = uid
1170 |
1171 | if privacy is not None:
1172 | args['privacy'] = privacy
1173 |
1174 | args = self._client._build_post_args(
1175 | 'facebook.video.upload',
1176 | self._client._add_session_args(args))
1177 |
1178 | try:
1179 | import cStringIO as StringIO
1180 | except ImportError:
1181 | import StringIO
1182 |
1183 | # check for a filename specified...if the user is passing binary data in
1184 | # video then a filename will be specified
1185 | if filename is None:
1186 | data = StringIO.StringIO(open(video, 'rb').read())
1187 | else:
1188 | # there was a filename specified, which indicates that video was not
1189 | # the path to an video file but rather the binary data of a file
1190 | data = StringIO.StringIO(video)
1191 | video = filename
1192 |
1193 | content_type, body = self.__encode_multipart_formdata(
1194 | list(args.iteritems()), [(video, data)])
1195 |
1196 | # Set the correct End Point Url, note this is different to all other FB
1197 | # EndPoints
1198 | urlinfo = urlparse.urlsplit(FACEBOOK_VIDEO_URL)
1199 | try:
1200 | content_length = len(body)
1201 | chunk_size = 4096
1202 |
1203 | h = httplib.HTTPConnection(urlinfo[1])
1204 | h.putrequest('POST', urlinfo[2])
1205 | h.putheader('Content-Type', content_type)
1206 | h.putheader('Content-Length', str(content_length))
1207 | h.putheader('MIME-Version', '1.0')
1208 | h.putheader('User-Agent', 'PyFacebook Client Library')
1209 | h.endheaders()
1210 |
1211 | if callback:
1212 | count = 0
1213 | while len(body) > 0:
1214 | if len(body) < chunk_size:
1215 | data = body
1216 | body = ''
1217 | else:
1218 | data = body[0:chunk_size]
1219 | body = body[chunk_size:]
1220 |
1221 | h.send(data)
1222 | count += 1
1223 | callback(count, chunk_size, content_length)
1224 | else:
1225 | h.send(body)
1226 |
1227 | response = h.getresponse()
1228 |
1229 | if response.status != 200:
1230 | raise Exception(
1231 | 'Error uploading video: Facebook returned HTTP %s (%s)' %
1232 | (response.status, response.reason))
1233 | response = response.read()
1234 | except:
1235 | # sending the photo failed, perhaps we are using GAE
1236 | try:
1237 | from google.appengine.api import urlfetch
1238 |
1239 | try:
1240 | response = urlread(
1241 | url=FACEBOOK_VIDEO_URL,
1242 | data=body,
1243 | headers={
1244 | 'POST': urlinfo[2],
1245 | 'Content-Type': content_type,
1246 | 'MIME-Version': '1.0'})
1247 | except urllib2.URLError:
1248 | raise Exception(
1249 | 'Error uploading video: Facebook returned %s' %
1250 | (response))
1251 | except ImportError:
1252 | # could not import from google.appengine.api, so we are not
1253 | # running in GAE
1254 | raise Exception('Error uploading video.')
1255 |
1256 | return self._client._parse_response(response, 'facebook.video.upload')
1257 |
1258 | def __encode_multipart_formdata(self, fields, files):
1259 | """Encodes a multipart/form-data message to upload an image."""
1260 | boundary = '-------tHISiStheMulTIFoRMbOUNDaRY'
1261 | crlf = '\r\n'
1262 | l = []
1263 |
1264 | for (key, value) in fields:
1265 | l.append('--' + boundary)
1266 | l.append('Content-Disposition: form-data; name="%s"' % str(key))
1267 | l.append('')
1268 | l.append(str(value))
1269 | for (filename, value) in files:
1270 | l.append('--' + boundary)
1271 | l.append(
1272 | 'Content-Disposition: form-data; filename="%s"' %
1273 | (str(filename), ))
1274 | l.append('Content-Type: %s' % self.__get_content_type(filename))
1275 | l.append('')
1276 | l.append(value.getvalue())
1277 | l.append('--' + boundary + '--')
1278 | l.append('')
1279 | body = crlf.join(l)
1280 | content_type = 'multipart/form-data; boundary=%s' % boundary
1281 | return content_type, body
1282 |
1283 | def __get_content_type(self, filename):
1284 | """Returns a guess at the MIME type of the file from the filename."""
1285 | return str(mimetypes.guess_type(filename)[
1286 | 0]) or 'application/octet-stream'
1287 |
1288 |
1289 | class Facebook(object):
1290 | """
1291 | Provides access to the Facebook API.
1292 |
1293 | Instance Variables:
1294 |
1295 | added
1296 | True if the user has added this application.
1297 |
1298 | api_key
1299 | Your API key, as set in the constructor.
1300 |
1301 | app_id
1302 | Your application id, as set in the constructor or fetched from
1303 | fb_sig_app_id request parameter.
1304 |
1305 | app_name
1306 | Your application's name, i.e. the APP_NAME in http://apps.facebook.com/APP_NAME/ if
1307 | this is for an internal web application. Optional, but useful for automatic redirects
1308 | to canvas pages.
1309 |
1310 | auth_token
1311 | The auth token that Facebook gives you, either with facebook.auth.createToken,
1312 | or through a GET parameter.
1313 |
1314 | callback_path
1315 | The path of the callback set in the Facebook app settings. If your callback is set
1316 | to http://www.example.com/facebook/callback/, this should be '/facebook/callback/'.
1317 | Optional, but useful for automatic redirects back to the same page after login.
1318 |
1319 | desktop
1320 | True if this is a desktop app, False otherwise. Used for determining how to
1321 | authenticate.
1322 |
1323 | ext_perms
1324 | Any extended permissions that the user has granted to your application.
1325 | This parameter is set only if the user has granted any.
1326 |
1327 | facebook_url
1328 | The url to use for Facebook requests.
1329 |
1330 | facebook_secure_url
1331 | The url to use for secure Facebook requests.
1332 |
1333 | in_canvas
1334 | True if the current request is for a canvas page.
1335 |
1336 | in_iframe
1337 | True if the current request is for an HTML page to embed in Facebook inside an iframe.
1338 |
1339 | is_session_from_cookie
1340 | True if the current request session comes from a session cookie.
1341 |
1342 | in_profile_tab
1343 | True if the current request is for a user's tab for your application.
1344 |
1345 | internal
1346 | True if this Facebook object is for an internal application (one that can be added on Facebook)
1347 |
1348 | locale
1349 | The user's locale. Default: 'en_US'
1350 |
1351 | oauth2:
1352 | Whether to use the new OAuth 2.0 authentication mechanism. Default: False
1353 |
1354 | oauth2_token:
1355 | The current OAuth 2.0 token.
1356 |
1357 | oauth2_token_expires:
1358 | The UNIX time when the OAuth 2.0 token expires (seconds).
1359 |
1360 | page_id
1361 | Set to the page_id of the current page (if any)
1362 |
1363 | profile_update_time
1364 | The time when this user's profile was last updated. This is a UNIX timestamp. Default: None if unknown.
1365 |
1366 | secret
1367 | Secret that is used after getSession for desktop apps.
1368 |
1369 | secret_key
1370 | Your application's secret key, as set in the constructor.
1371 |
1372 | session_key
1373 | The current session key. Set automatically by auth.getSession, but can be set
1374 | manually for doing infinite sessions.
1375 |
1376 | session_key_expires
1377 | The UNIX time of when this session key expires, or 0 if it never expires.
1378 |
1379 | uid
1380 | After a session is created, you can get the user's UID with this variable. Set
1381 | automatically by auth.getSession.
1382 |
1383 | ----------------------------------------------------------------------
1384 |
1385 | """
1386 |
1387 | def __init__(self, api_key, secret_key, auth_token=None, app_name=None,
1388 | callback_path=None, internal=None, proxy=None,
1389 | facebook_url=None, facebook_secure_url=None,
1390 | generate_session_secret=0, app_id=None, oauth2=False,
1391 | iframe_validation_timeout=10):
1392 | """
1393 | Initializes a new Facebook object which provides wrappers for the Facebook API.
1394 |
1395 | If this is a desktop application, the next couple of steps you might want to take are:
1396 |
1397 | facebook.auth.createToken() # create an auth token
1398 | facebook.login() # show a browser window
1399 | wait_login() # somehow wait for the user to log in
1400 | facebook.auth.getSession() # get a session key
1401 |
1402 | For web apps, if you are passed an auth_token from Facebook, pass that in as a named parameter.
1403 | Then call:
1404 |
1405 | facebook.auth.getSession()
1406 |
1407 | """
1408 | self.api_key = api_key
1409 | self.secret_key = secret_key
1410 | self.app_id = app_id
1411 | self.oauth2 = oauth2
1412 | self.oauth2_token = None
1413 | self.oauth2_token_expires = None
1414 | self.session_key = None
1415 | self.session_key_expires = None
1416 | self.auth_token = auth_token
1417 | self.secret = None
1418 | self.generate_session_secret = generate_session_secret
1419 | self.uid = None
1420 | self.page_id = None
1421 | self.in_canvas = False
1422 | self.in_iframe = False
1423 | self.is_session_from_cookie = False
1424 | self.in_profile_tab = False
1425 | self.added = False
1426 | self.app_name = app_name
1427 | self.callback_path = callback_path
1428 | self.internal = internal
1429 | self._friends = None
1430 | self.locale = 'en_US'
1431 | self.profile_update_time = None
1432 | self.ext_perms = []
1433 | self.proxy = proxy
1434 | self.iframe_validation_timeout = iframe_validation_timeout
1435 | if facebook_url is None:
1436 | self.facebook_url = FACEBOOK_URL
1437 | else:
1438 | self.facebook_url = facebook_url
1439 | if facebook_secure_url is None:
1440 | self.facebook_secure_url = FACEBOOK_SECURE_URL
1441 | else:
1442 | self.facebook_secure_url = facebook_secure_url
1443 |
1444 | for namespace in METHODS:
1445 | self.__dict__[namespace] = eval(
1446 | '%sProxy(self, \'%s\')' %
1447 | (namespace.title(),
1448 | 'facebook.%s' %
1449 | namespace))
1450 |
1451 | def _hash_args(self, args, secret=None):
1452 | """Hashes arguments by joining key=value pairs, appending a secret, and then taking the MD5 hex digest."""
1453 | # @author: houyr
1454 | # fix for UnicodeEncodeError
1455 | hasher = hashlib.md5(
1456 | ''.join(
1457 | [
1458 | '%s=%s' %
1459 | (isinstance(
1460 | x,
1461 | unicode) and x.encode("utf-8") or x,
1462 | isinstance(
1463 | args[x],
1464 | unicode) and args[x].encode("utf-8") or args[x]) for x in sorted(
1465 | args.keys())]))
1466 | if secret:
1467 | hasher.update(secret)
1468 | elif self.secret:
1469 | hasher.update(self.secret)
1470 | else:
1471 | hasher.update(self.secret_key)
1472 | return hasher.hexdigest()
1473 |
1474 | def _parse_response_item(self, node):
1475 | """Parses an XML response node from Facebook."""
1476 | if node.nodeType == node.DOCUMENT_NODE and \
1477 | node.childNodes[0].hasAttributes() and \
1478 | node.childNodes[0].hasAttribute('list') and \
1479 | node.childNodes[0].getAttribute('list') == "true":
1480 | return {
1481 | node.childNodes[0].nodeName: self._parse_response_list(
1482 | node.childNodes[0])}
1483 | elif node.nodeType == node.ELEMENT_NODE and \
1484 | node.hasAttributes() and \
1485 | node.hasAttribute('list') and \
1486 | node.getAttribute('list') == "true":
1487 | return self._parse_response_list(node)
1488 | elif len(filter(lambda x: x.nodeType == x.ELEMENT_NODE, node.childNodes)) > 0:
1489 | return self._parse_response_dict(node)
1490 | else:
1491 | return ''.join(
1492 | node.data for node in node.childNodes if node.nodeType == node.TEXT_NODE)
1493 |
1494 | def _parse_response_dict(self, node):
1495 | """Parses an XML dictionary response node from Facebook."""
1496 | result = {}
1497 | for item in filter(
1498 | lambda x: x.nodeType == x.ELEMENT_NODE,
1499 | node.childNodes):
1500 | result[item.nodeName] = self._parse_response_item(item)
1501 | if node.nodeType == node.ELEMENT_NODE and node.hasAttributes():
1502 | if node.hasAttribute('id'):
1503 | result['id'] = node.getAttribute('id')
1504 | return result
1505 |
1506 | def _parse_response_list(self, node):
1507 | """Parses an XML list response node from Facebook."""
1508 | result = []
1509 | for item in filter(
1510 | lambda x: x.nodeType == x.ELEMENT_NODE,
1511 | node.childNodes):
1512 | result.append(self._parse_response_item(item))
1513 | return result
1514 |
1515 | def _check_error(self, response):
1516 | """Checks if the given Facebook response is an error, and then raises the appropriate exception."""
1517 | if isinstance(response, dict) and 'error_code' in response:
1518 | raise FacebookError(
1519 | response['error_code'],
1520 | response['error_msg'],
1521 | response['request_args'])
1522 |
1523 | def _build_post_args(self, method, args=None, signed=False):
1524 | """Adds to args parameters that are necessary for every call to the API."""
1525 | if args is None:
1526 | args = {}
1527 |
1528 | for arg in args.items():
1529 | if isinstance(arg[1], list):
1530 | args[arg[0]] = ','.join(str(a) for a in arg[1])
1531 | elif isinstance(arg[1], unicode):
1532 | args[arg[0]] = arg[1].encode("UTF-8")
1533 | elif isinstance(arg[1], bool):
1534 | args[arg[0]] = str(arg[1]).lower()
1535 |
1536 | args['method'] = method
1537 | args['format'] = RESPONSE_FORMAT
1538 | if not signed and self.oauth2 and self.oauth2_token:
1539 | args['access_token'] = self.oauth2_token
1540 | else:
1541 | args['api_key'] = self.api_key
1542 | args['v'] = '1.0'
1543 | args['sig'] = self._hash_args(args)
1544 |
1545 | return args
1546 |
1547 | def _add_session_args(self, args=None):
1548 | """Adds 'session_key' and 'call_id' to args, which are used for API calls that need sessions."""
1549 | if args is None:
1550 | args = {}
1551 |
1552 | if not self.session_key:
1553 | return args
1554 | # some calls don't need a session anymore. this might be better done in the markup
1555 | # raise RuntimeError('Session key not set. Make sure auth.getSession has been called.')
1556 |
1557 | if not self.oauth2 or not self.oauth2_token:
1558 | args['session_key'] = self.session_key
1559 | args['call_id'] = str(int(time.time() * 1000))
1560 |
1561 | return args
1562 |
1563 | def _parse_response(self, response, method, format=None):
1564 | """Parses the response according to the given (optional) format, which should be either 'JSON' or 'XML'."""
1565 | if not format:
1566 | format = RESPONSE_FORMAT
1567 |
1568 | if format == 'JSON':
1569 | result = simplejson.loads(response)
1570 |
1571 | self._check_error(result)
1572 | elif format == 'XML':
1573 | dom = minidom.parseString(response)
1574 | result = self._parse_response_item(dom)
1575 | dom.unlink()
1576 |
1577 | if 'error_response' in result:
1578 | self._check_error(result['error_response'])
1579 |
1580 | result = result[method[9:].replace('.', '_') + '_response']
1581 | else:
1582 | raise RuntimeError('Invalid format specified.')
1583 |
1584 | return result
1585 |
1586 | def hash_email(self, email):
1587 | """
1588 | Hash an email address in a format suitable for Facebook Connect.
1589 |
1590 | """
1591 | email = email.lower().strip()
1592 | return "%s_%s" % (
1593 | struct.unpack("I", struct.pack("i", binascii.crc32(email)))[0],
1594 | hashlib.md5(email).hexdigest(),
1595 | )
1596 |
1597 | def unicode_urlencode(self, params):
1598 | """
1599 | @author: houyr
1600 | A unicode aware version of urllib.urlencode.
1601 | """
1602 | if isinstance(params, dict):
1603 | params = params.items()
1604 | return urllib.urlencode([(k, isinstance(v, unicode) and v.encode('utf-8') or v)
1605 | for k, v in params])
1606 |
1607 | def __call__(self, method=None, args=None, secure=False, signed=False):
1608 | """Make a call to Facebook's REST server."""
1609 | # for Django templates, if this object is called without any arguments
1610 | # return the object itself
1611 | if method is None:
1612 | return self
1613 |
1614 | # __init__ hard-codes into en_US
1615 | if args is not None and 'locale' not in args:
1616 | args['locale'] = self.locale
1617 |
1618 | # @author: houyr
1619 | # fix for bug of UnicodeEncodeError
1620 | post_data = self.unicode_urlencode(
1621 | self._build_post_args(method, args, signed))
1622 |
1623 | if self.proxy:
1624 | proxy_handler = urllib2.ProxyHandler(self.proxy)
1625 | opener = urllib2.build_opener(proxy_handler)
1626 | if self.oauth2 or secure:
1627 | response = opener.open(
1628 | self.facebook_secure_url, post_data).read()
1629 | else:
1630 | response = opener.open(self.facebook_url, post_data).read()
1631 | else:
1632 | if self.oauth2 or secure:
1633 | response = urlread(self.facebook_secure_url, post_data)
1634 | else:
1635 | response = urlread(self.facebook_url, post_data)
1636 |
1637 | return self._parse_response(response, method)
1638 |
1639 | def oauth2_access_token(self, code, next, required_permissions=None):
1640 | """
1641 | We've called authorize, and received a code, now we need to convert
1642 | this to an access_token
1643 |
1644 | """
1645 | args = {
1646 | 'client_id': self.app_id,
1647 | 'client_secret': self.secret_key,
1648 | 'redirect_uri': next,
1649 | 'code': code
1650 | }
1651 |
1652 | if required_permissions:
1653 | args['scope'] = ",".join(required_permissions)
1654 |
1655 | # TODO see if immediate param works as per OAuth 2.0 spec?
1656 | url = self.get_graph_url('oauth/access_token', **args)
1657 |
1658 | if self.proxy:
1659 | proxy_handler = urllib2.ProxyHandler(self.proxy)
1660 | opener = urllib2.build_opener(proxy_handler)
1661 | response = opener.open(url).read()
1662 | else:
1663 | response = urlread(url)
1664 |
1665 | result = urlparse.parse_qs(response)
1666 | self.oauth2_token = result['access_token'][0]
1667 | self.oauth2_token_expires = time.time() + int(result['expires'][0])
1668 |
1669 | # URL helpers
1670 | def get_url(self, page, **args):
1671 | """
1672 | Returns one of the Facebook URLs (www.facebook.com/SOMEPAGE.php).
1673 | Named arguments are passed as GET query string parameters.
1674 |
1675 | """
1676 | return 'http://www.facebook.com/%s.php?%s' % (
1677 | page, urllib.urlencode(args))
1678 |
1679 | def get_app_url(self, path=''):
1680 | """
1681 | Returns the URL for this app's canvas page, according to app_name.
1682 |
1683 | """
1684 | return 'http://apps.facebook.com/%s/%s' % (self.app_name, path)
1685 |
1686 | def get_graph_url(self, path='', **args):
1687 | """
1688 | Returns the URL for the graph API with the supplied path and parameters
1689 |
1690 | """
1691 | return 'https://graph.facebook.com/%s?%s' % (
1692 | path, urllib.urlencode(args))
1693 |
1694 | def get_add_url(self, next=None):
1695 | """
1696 | Returns the URL that the user should be redirected to in order to add the application.
1697 |
1698 | """
1699 | args = {'api_key': self.api_key, 'v': '1.0'}
1700 |
1701 | if next is not None:
1702 | args['next'] = next
1703 |
1704 | return self.get_url('install', **args)
1705 |
1706 | def get_authorize_url(self, next=None, next_cancel=None):
1707 | """
1708 | Returns the URL that the user should be redirected to in order to
1709 | authorize certain actions for application.
1710 |
1711 | """
1712 | args = {'api_key': self.api_key, 'v': '1.0'}
1713 |
1714 | if next is not None:
1715 | args['next'] = next
1716 |
1717 | if next_cancel is not None:
1718 | args['next_cancel'] = next_cancel
1719 |
1720 | return self.get_url('authorize', **args)
1721 |
1722 | def get_login_url(self, next=None, popup=False, canvas=True,
1723 | required_permissions=None):
1724 | """
1725 | Returns the URL that the user should be redirected to in order to login.
1726 |
1727 | next -- the URL that Facebook should redirect to after login
1728 | required_permissions -- permission required by the application
1729 |
1730 | """
1731 | if self.oauth2:
1732 | args = {
1733 | 'client_id': self.app_id,
1734 | 'redirect_uri': next
1735 | }
1736 |
1737 | if required_permissions:
1738 | args['scope'] = required_permissions
1739 |
1740 | if popup:
1741 | args['display'] = 'popup'
1742 |
1743 | return self.get_graph_url('oauth/authorize', **args)
1744 | else:
1745 | args = {'api_key': self.api_key, 'v': '1.0'}
1746 |
1747 | if next is not None:
1748 | args['next'] = next
1749 |
1750 | if canvas is True:
1751 | args['canvas'] = 1
1752 |
1753 | if popup is True:
1754 | args['popup'] = 1
1755 |
1756 | if required_permissions:
1757 | args['req_perms'] = ",".join(required_permissions)
1758 |
1759 | if self.auth_token is not None:
1760 | args['auth_token'] = self.auth_token
1761 |
1762 | return self.get_url('login', **args)
1763 |
1764 | def login(self, popup=False):
1765 | """Open a web browser telling the user to login to Facebook."""
1766 | import webbrowser
1767 | webbrowser.open(self.get_login_url(popup=popup))
1768 |
1769 | def get_ext_perm_url(self, ext_perm, next=None, popup=False):
1770 | """
1771 | Returns the URL that the user should be redirected to in order to grant an extended permission.
1772 |
1773 | ext_perm -- the name of the extended permission to request
1774 | next -- the URL that Facebook should redirect to after login
1775 |
1776 | """
1777 | args = {'ext_perm': ext_perm, 'api_key': self.api_key, 'v': '1.0'}
1778 |
1779 | if next is not None:
1780 | args['next'] = next
1781 |
1782 | if popup is True:
1783 | args['popup'] = 1
1784 |
1785 | return self.get_url('authorize', **args)
1786 |
1787 | def request_extended_permission(self, ext_perm, popup=False):
1788 | """Open a web browser telling the user to grant an extended permission."""
1789 | import webbrowser
1790 | webbrowser.open(self.get_ext_perm_url(ext_perm, popup=popup))
1791 |
1792 | def check_session(self, request, params=None):
1793 | """
1794 | Checks the given Django HttpRequest for Facebook parameters such as
1795 | POST variables or an auth token. If the session is valid, returns True
1796 | and this object can now be used to access the Facebook API. Otherwise,
1797 | it returns False, and the application should take the appropriate action
1798 | (either log the user in or have him add the application).
1799 |
1800 | """
1801 | self.in_canvas = (request.POST.get('fb_sig_in_canvas') == '1')
1802 |
1803 | if 'auth_token' not in request.GET and self.session_key and (
1804 | self.uid or self.page_id):
1805 | return True
1806 |
1807 | if not params:
1808 | if request.method == 'POST':
1809 | params = self.validate_signature(request.POST)
1810 | else:
1811 | if 'installed' in request.GET:
1812 | self.added = True
1813 |
1814 | if 'fb_page_id' in request.GET:
1815 | self.page_id = request.GET['fb_page_id']
1816 |
1817 | if 'auth_token' in request.GET:
1818 | self.added = True # added by Marinho
1819 | self.auth_token = request.GET['auth_token']
1820 |
1821 | try:
1822 | self.auth.getSession()
1823 | except FacebookError as e:
1824 | self.auth_token = None
1825 | return False
1826 |
1827 | return True
1828 |
1829 | params = self.validate_signature(request.GET)
1830 |
1831 | if not params:
1832 | cookies = None
1833 |
1834 | # first check if we are in django - to check cookies
1835 | if hasattr(request, 'COOKIES'):
1836 | cookies = request.COOKIES
1837 | else:
1838 | if hasattr(request, 'cookies'):
1839 | cookies = request.cookies
1840 |
1841 | if cookies:
1842 |
1843 | params = self.validate_oauth_cookie_signature(cookies)
1844 |
1845 | if params:
1846 | self.is_oauth = True
1847 | self.oauth_token = params['access_token']
1848 | else:
1849 | params = self.validate_cookie_signature(cookies)
1850 | self.is_session_from_cookie = True
1851 |
1852 | if not params:
1853 | if self.validate_iframe(request):
1854 | return True
1855 | else:
1856 | params = {}
1857 |
1858 | if not params:
1859 | return False
1860 |
1861 | if 'in_canvas' in params and params.get('in_canvas') == '1':
1862 | self.in_canvas = True
1863 |
1864 | if 'in_iframe' in params and params.get('in_iframe') == '1':
1865 | self.in_iframe = True
1866 |
1867 | if 'in_profile_tab' in params and params.get('in_profile_tab') == '1':
1868 | self.in_profile_tab = True
1869 |
1870 | if 'added' in params and params.get('added') == '1':
1871 | self.added = True
1872 |
1873 | if params.get('expires'):
1874 | # Marinho Brandao - fixing problem with no session
1875 | self.session_key_expires = params.get(
1876 | 'expires', '').isdigit() and int(
1877 | params['expires']) or 0
1878 |
1879 | if 'locale' in params:
1880 | self.locale = params['locale']
1881 |
1882 | if 'profile_update_time' in params:
1883 | try:
1884 | self.profile_update_time = int(params['profile_update_time'])
1885 | except ValueError:
1886 | pass
1887 |
1888 | if 'ext_perms' in params:
1889 | if params['ext_perms']:
1890 | self.ext_perms = params['ext_perms'].split(',')
1891 | else:
1892 | self.ext_perms = []
1893 |
1894 | if 'friends' in params:
1895 | if params['friends']:
1896 | self._friends = params['friends'].split(',')
1897 | else:
1898 | self._friends = []
1899 |
1900 | # If app_id is not set explicitly, pick it up from the param
1901 | if not self.app_id and 'app_id' in params:
1902 | self.app_id = params['app_id']
1903 |
1904 | if 'session_key' in params:
1905 | self.session_key = params['session_key']
1906 | if 'user' in params:
1907 | self.uid = params['user']
1908 | elif 'page_id' in params:
1909 | self.page_id = params['page_id']
1910 | elif 'uid' in params:
1911 | self.uid = params['uid']
1912 | else:
1913 | return False
1914 | elif 'profile_session_key' in params:
1915 | self.session_key = params['profile_session_key']
1916 | if 'profile_user' in params:
1917 | self.uid = params['profile_user']
1918 | else:
1919 | return False
1920 | elif 'canvas_user' in params:
1921 | self.uid = params['canvas_user']
1922 | elif 'uninstall' in params:
1923 | self.uid = params['user']
1924 | else:
1925 | return False
1926 |
1927 | return True
1928 |
1929 | def validate_oauth_session(self, session_json):
1930 | session = simplejson.loads(session_json)
1931 | sig = session.pop('sig')
1932 | hash = self._hash_args(session)
1933 | if hash == sig:
1934 | return session
1935 | return None
1936 |
1937 | def validate_signature(self, post, prefix='fb_sig', timeout=None):
1938 | """
1939 | Validate parameters passed to an internal Facebook app from Facebook.
1940 |
1941 | """
1942 | args = post.copy()
1943 |
1944 | if prefix not in args:
1945 | return None
1946 |
1947 | del args[prefix]
1948 |
1949 | if timeout and '%s_time' % prefix in post and time.time(
1950 | ) - float(post['%s_time' % prefix]) > timeout:
1951 | return None
1952 |
1953 | args = dict([(key[len(prefix + '_'):], value)
1954 | for key, value in args.items() if key.startswith(prefix)])
1955 |
1956 | hash = self._hash_args(args)
1957 |
1958 | if hash == post[prefix]:
1959 | return args
1960 | else:
1961 | return None
1962 |
1963 | def validate_iframe(self, request):
1964 | request_dict = request.POST if request.method == 'POST' else request.GET
1965 | if any(
1966 | key not in request_dict for key in [
1967 | 'userid',
1968 | 'reqtime',
1969 | 'appsig']):
1970 | return False
1971 | request_time = request_dict['reqtime']
1972 | time_now = int(time.time())
1973 | if time_now - int(request_time) > self.iframe_validation_timeout:
1974 | return False
1975 | userid = int(request_dict['userid'])
1976 | self.uid = userid
1977 | app_sig = request_dict['appsig']
1978 | digest = create_hmac(
1979 | self.secret_key, "%s%s" %
1980 | (str(userid), str(request_time)))
1981 | return digest == app_sig
1982 |
1983 | def validate_oauth_cookie_signature(self, cookies):
1984 | cookie_name = 'fbs_%s' % self.app_id
1985 | if cookie_name in cookies:
1986 | # value like
1987 | # "access_token=104302089510310%7C2.HYYLow1Vlib0s_sJSAESjw__.3600.1275037200-100000214342553%7CtC1aolM22Lauj_dZhYnv_tF2CK4.&base_domain=yaycy.com&expires=1275037200&secret=FvIHkbAFwEy_0sueRk2ZYQ__&session_key=2.HYYoow1Vlib0s_sJSAESjw__.3600.1275037200-100000214342553&sig=7bb035a0411be7aa801964ae34416f28&uid=100000214342553"
1988 | params = dict([part.split('=')
1989 | for part in cookies[cookie_name].split('&')])
1990 | sig = params.pop('sig')
1991 | hash = self._hash_args(params)
1992 | if hash == sig:
1993 | return params
1994 | return None
1995 |
1996 | def validate_cookie_signature(self, cookies):
1997 | """
1998 | Validate parameters passed by cookies, namely facebookconnect or js api.
1999 | """
2000 |
2001 | api_key = self.api_key
2002 | if api_key not in cookies:
2003 | return None
2004 |
2005 | prefix = api_key + "_"
2006 |
2007 | params = {}
2008 | vals = ''
2009 | for k in sorted(cookies):
2010 | if k.startswith(prefix):
2011 | key = k.replace(prefix, "")
2012 | value = cookies[k]
2013 | if value != 'None':
2014 | params[key] = value
2015 | vals += '%s=%s' % (key, value)
2016 |
2017 | hasher = hashlib.md5(vals)
2018 |
2019 | hasher.update(self.secret_key)
2020 | digest = hasher.hexdigest()
2021 | if digest == cookies[api_key]:
2022 | params['is_session_from_cookie'] = True
2023 | return params
2024 | else:
2025 | return False
2026 |
2027 |
2028 | if __name__ == '__main__':
2029 | # sample desktop application
2030 |
2031 | api_key = ''
2032 | secret_key = ''
2033 |
2034 | facebook = Facebook(api_key, secret_key)
2035 |
2036 | facebook.auth.createToken()
2037 |
2038 | # Show login window
2039 | # Set popup=True if you want login without navigational elements
2040 | facebook.login()
2041 |
2042 | # Login to the window, then press enter
2043 | print 'After logging in, press enter...'
2044 | raw_input()
2045 |
2046 | facebook.auth.getSession()
2047 | print 'Session Key: ', facebook.session_key
2048 | print 'Your UID: ', facebook.uid
2049 |
2050 | info = facebook.users.getInfo(
2051 | [facebook.uid], ['name', 'birthday', 'affiliations', 'sex'])[0]
2052 |
2053 | print 'Your Name: ', info['name']
2054 | print 'Your Birthday: ', info['birthday']
2055 | print 'Your Gender: ', info['sex']
2056 |
2057 | friends = facebook.friends.get()
2058 | friends = facebook.users.getInfo(
2059 | friends[
2060 | 0:5], [
2061 | 'name', 'birthday', 'relationship_status'])
2062 |
2063 | for friend in friends:
2064 | print friend['name'], 'has a birthday on', friend['birthday'], 'and is', friend['relationship_status']
2065 |
2066 | arefriends = facebook.friends.areFriends(
2067 | [friends[0]['uid']], [friends[1]['uid']])
2068 |
2069 | photos = facebook.photos.getAlbums(facebook.uid)
2070 |
--------------------------------------------------------------------------------
/facebook/djangofb/__init__.py:
--------------------------------------------------------------------------------
1 | import re
2 | import time
3 | import datetime
4 | import facebook
5 |
6 | from django.http import HttpResponse, HttpResponseRedirect
7 | from django.utils.http import urlquote
8 | from django.core.exceptions import ImproperlyConfigured
9 | from django.conf import settings
10 |
11 | try:
12 | from threading import local
13 | except ImportError:
14 | from django.utils._threading_local import local
15 |
16 | __all__ = [
17 | 'Facebook',
18 | 'FacebookMiddleware',
19 | 'get_facebook_client',
20 | 'require_login',
21 | 'require_add']
22 |
23 | _thread_locals = local()
24 |
25 |
26 | class Facebook(facebook.Facebook):
27 |
28 | def redirect(self, url):
29 | """
30 | Helper for Django which redirects to another page. If inside a
31 | canvas page, writes a instead to achieve the same effect.
32 |
33 | """
34 | if self.in_canvas:
35 | return HttpResponse('' % (url,))
36 | elif re.search("^https?:\/\/([^\/]*\.)?facebook\.com(:\d+)?", url.lower()):
37 | return HttpResponse(
38 | '' %
39 | url)
40 | else:
41 | return HttpResponseRedirect(url)
42 |
43 | def url_for(self, path):
44 | """
45 | Expand the path into a full URL, depending on whether we're in a canvas
46 | page or not.
47 |
48 | """
49 | if self.in_canvas:
50 | return self.get_app_url(path[1:])
51 | else:
52 | return '%s%s' % (settings.SITE_URL, path)
53 |
54 | def _oauth2_process_params(self, request):
55 | """
56 | Check a few key parameters for oauth methods
57 |
58 | """
59 | self.in_canvas = (request.REQUEST.get('fb_sig_in_canvas') == '1')
60 | self.added = (request.REQUEST.get('fb_sig_added') == '1')
61 | # If app_id is not set explicitly, pick it up from the params
62 | if not self.app_id:
63 | self.app_id = request.REQUEST.get('fb_sig_app_id')
64 | if not self.uid:
65 | self.uid = request.REQUEST.get('fb_sig_user')
66 |
67 | def oauth2_check_session(self, request):
68 | """
69 | Check to see if we have an access_token in our session
70 |
71 | """
72 | valid_token = False
73 |
74 | # See if they're in the request
75 | if 'session' in request.POST:
76 | print 'session from POST'
77 | values = self.validate_oauth_session(request.POST['session'])
78 |
79 | # Might be in the query string (e.g. from iframe)
80 | elif 'session' in request.GET:
81 | print 'session from GET'
82 | values = self.validate_oauth_session(request.GET['session'])
83 |
84 | # Look out for an access_token in our cookies from the JS SDK FB.init
85 | elif request.COOKIES:
86 | values = self.validate_oauth_cookie_signature(request.COOKIES)
87 | print 'session from COOKIE %s' % values
88 |
89 | if values and 'access_token' in values:
90 | request.session['oauth2_token'] = values['access_token']
91 | request.session['oauth2_token_expires'] = values['expires']
92 | self.session_key = values['session_key']
93 | self.uid = values['uid']
94 | self.added = True
95 |
96 | # If we've been accepted by the user
97 | if self.added:
98 |
99 | # See if we've got this user's access_token in our session
100 | if 'oauth2_token' in request.session:
101 | self.oauth2_token = request.session['oauth2_token']
102 | self.oauth2_token_expires = request.session[
103 | 'oauth2_token_expires']
104 |
105 | if self.oauth2_token_expires:
106 | if self.oauth2_token_expires > time.time():
107 | # Got a token, and it's valid
108 | valid_token = True
109 | else:
110 | del request.session['oauth2_token']
111 | del request.session['oauth2_token_expires']
112 |
113 | return valid_token
114 |
115 | def oauth2_check_permissions(self, request, required_permissions,
116 | additional_permissions=None,
117 | fql_check=True, force_check=True):
118 | """
119 | Check for specific extended_permissions.
120 |
121 | If fql_check is True (default), oauth2_check_session() should be called
122 | first to ensure the access_token is in place and valid to make query.
123 |
124 | """
125 | has_permissions = False
126 |
127 | req_perms = set(required_permissions.split(','))
128 |
129 | if 'oauth2_extended_permissions' in request.session:
130 | cached_perms = request.session['oauth2_extended_permissions']
131 |
132 | # so now, fb_sig_ext_perms seems to contain the right perms (!)
133 |
134 | if not force_check and cached_perms and req_perms.issubset(
135 | cached_perms):
136 | # Note that this has the potential to be out of date!
137 | has_permissions = True
138 | elif fql_check:
139 | # TODO allow option to use preload FQL for this?
140 | perms_query = required_permissions
141 |
142 | # Note that we can query additional permissions that we
143 | # don't require. This can be useful for optional
144 | # functionality (or simply for better caching)
145 | if additional_permissions:
146 | perms_query += ',' + additional_permissions
147 |
148 | perms_results = self.fql.query(
149 | 'select %s from permissions where uid=%s' %
150 | (perms_query, self.uid))[0]
151 | actual_perms = set()
152 | for permission, allowed in perms_results.items():
153 | if allowed == 1:
154 | actual_perms.add(permission)
155 | request.session['oauth2_extended_permissions'] = actual_perms
156 | has_permissions = req_perms.issubset(actual_perms)
157 |
158 | return has_permissions
159 |
160 | def oauth2_process_code(self, request, redirect_uri):
161 | """
162 | Convert the code into an access_token.
163 |
164 | """
165 | if 'code' in request.GET:
166 | # We've got a code from an authorisation, so convert it to a
167 | # access_token
168 |
169 | self.oauth2_access_token(request.GET['code'], next=redirect_uri)
170 |
171 | request.session['oauth2_token'] = self.oauth2_token
172 | request.session['oauth2_token_expires'] = self.oauth2_token_expires
173 |
174 | return True
175 | # else: 'error_reason' in request.GET
176 |
177 | return False
178 |
179 |
180 | def get_facebook_client():
181 | """
182 | Get the current Facebook object for the calling thread.
183 |
184 | """
185 | try:
186 | return _thread_locals.facebook
187 | except AttributeError:
188 | raise ImproperlyConfigured(
189 | 'Make sure you have the Facebook middleware installed.')
190 |
191 |
192 | def _check_middleware(request):
193 | try:
194 | fb = request.facebook
195 | except:
196 | raise ImproperlyConfigured(
197 | 'Make sure you have the Facebook middleware installed.')
198 |
199 | if not fb.oauth2:
200 | raise ImproperlyConfigured(
201 | 'Please ensure that oauth2 is enabled (e.g. via settings.FACEBOOK_OAUTH2).')
202 |
203 | return fb
204 |
205 |
206 | def require_oauth(
207 | redirect_path=None,
208 | keep_state=True,
209 | in_canvas=True,
210 | required_permissions=None,
211 | check_permissions=None,
212 | force_check=True):
213 | """
214 | Decorator for Django views that requires the user to be OAuth 2.0'd.
215 | The FacebookMiddleware must be installed.
216 | Note that OAuth 2.0 does away with the app added/logged in distinction -
217 | it is now the case that users have now either authorised facebook users or
218 | not, and if they are, they may have granted the app a number of
219 | extended permissions - there is no lightweight/automatic login any more.
220 |
221 | Standard usage:
222 | @require_oauth()
223 | def some_view(request):
224 | ...
225 | """
226 | def decorator(view):
227 | def newview(request, *args, **kwargs):
228 | # permissions=newview.permissions
229 |
230 | try:
231 | fb = _check_middleware(request)
232 |
233 | valid_token = fb.oauth2_check_session(request)
234 |
235 | if required_permissions:
236 | has_permissions = fb.oauth2_check_permissions(
237 | request, required_permissions, check_permissions,
238 | valid_token, force_check)
239 | else:
240 | has_permissions = True
241 |
242 | if not valid_token or not has_permissions:
243 | if in_canvas:
244 | fb.in_canvas = in_canvas
245 |
246 | return _redirect_login(request, fb, redirect_path,
247 | keep_state, required_permissions)
248 |
249 | return view(request, *args, **kwargs)
250 | except facebook.FacebookError as e:
251 | # Invalid token (I think this can happen if the user logs out)
252 | # Unfortunately we don't find this out until we use the api
253 | if e.code == 190:
254 | del request.session['oauth2_token']
255 | del request.session['oauth2_token_expires']
256 | return _redirect_login(request, fb, redirect_path,
257 | keep_state, required_permissions)
258 | # newview.permissions = permissions
259 | return newview
260 | return decorator
261 |
262 |
263 | def _redirect_path(redirect_path, fb, path):
264 | """
265 | Resolve the path to use for the redirect_uri for authorization
266 |
267 | """
268 | if not redirect_path and fb.oauth2_redirect:
269 | redirect_path = fb.oauth2_redirect
270 | if redirect_path:
271 | if callable(redirect_path):
272 | redirect_path = redirect_path(path)
273 | else:
274 | redirect_path = path
275 | return redirect_path
276 |
277 |
278 | def _redirect_login(
279 | request,
280 | fb,
281 | redirect_path,
282 | keep_state,
283 | required_permissions):
284 | """
285 | Fully resolve the redirect path for an oauth login and add in any state
286 | info required to bring us back to the correct place afterwards
287 | """
288 | redirect_uri = fb.url_for(_redirect_path(redirect_path, fb, request.path))
289 |
290 | if keep_state:
291 | if callable(keep_state):
292 | state = keep_state(request)
293 | else:
294 | state = request.get_full_path()
295 | # passing state directly to facebook oauth endpoint doesn't work
296 | redirect_uri += '?state=%s' % urlquote(state)
297 |
298 | url = fb.get_login_url(next=redirect_uri,
299 | required_permissions=required_permissions)
300 |
301 | return fb.redirect(url)
302 |
303 |
304 | def process_oauth(restore_state=True, in_canvas=True):
305 | """
306 | Decorator for Django views that processes the user's code and converts it
307 | into an access_token.
308 | The FacebookMiddleware must be installed.
309 |
310 | Standard usage:
311 | @process_oauth()
312 | def some_view(request):
313 | ...
314 | """
315 | def decorator(view):
316 | def newview(request, *args, **kwargs):
317 | # permissions=newview.permissions
318 |
319 | fb = _check_middleware(request)
320 |
321 | if in_canvas:
322 | fb.in_canvas = in_canvas
323 |
324 | # Work out what the original redirect_uri value was
325 | redirect_uri = fb.url_for(_strip_code(request.get_full_path()))
326 |
327 | if fb.oauth2_process_code(request, redirect_uri):
328 | if restore_state:
329 | state = request.GET['state']
330 | if callable(restore_state):
331 | state = restore_state(state)
332 | else:
333 | state = fb.url_for(state)
334 | return fb.redirect(state)
335 |
336 | return view(request, *args, **kwargs)
337 | # newview.permissions = permissions
338 | return newview
339 | return decorator
340 |
341 |
342 | def _strip_code(path):
343 | """
344 | Restore the path to the original redirect_uri without the code parameter.
345 |
346 | """
347 | try:
348 | begin = path.find('&code')
349 | if begin == -1:
350 | begin = path.index('?code')
351 | end = path.find('&', begin + 1)
352 | if end == -1:
353 | end = len(path)
354 | return path[:begin] + path[end:]
355 | except ValueError:
356 | # no code, probably failed to authenticate
357 | # TODO strip error_reason instead here?
358 | return path
359 |
360 |
361 | def require_login(next=None, internal=None, required_permissions=None):
362 | """
363 | Decorator for Django views that requires the user to be logged in.
364 | The FacebookMiddleware must be installed.
365 |
366 | Standard usage:
367 | @require_login()
368 | def some_view(request):
369 | ...
370 |
371 | Redirecting after login:
372 | To use the 'next' parameter to redirect to a specific page after login, a callable should
373 | return a path relative to the Post-add URL. 'next' can also be an integer specifying how many
374 | parts of request.path to strip to find the relative URL of the canvas page. If 'next' is None,
375 | settings.callback_path and settings.app_name are checked to redirect to the same page after logging
376 | in. (This is the default behavior.)
377 | @require_login(next=some_callable)
378 | def some_view(request):
379 | ...
380 | """
381 | def decorator(view):
382 | def newview(request, *args, **kwargs):
383 | next = newview.next
384 | internal = newview.internal
385 |
386 | try:
387 | fb = request.facebook
388 | except:
389 | raise ImproperlyConfigured(
390 | 'Make sure you have the Facebook middleware installed.')
391 |
392 | if internal is None:
393 | internal = request.facebook.internal
394 |
395 | if callable(next):
396 | next = next(request.path)
397 | elif isinstance(next, int):
398 | next = '/'.join(request.path.split('/')[next + 1:])
399 | elif next is None and fb.callback_path and request.path.startswith(fb.callback_path):
400 | next = request.path[len(fb.callback_path):]
401 | elif not isinstance(next, str):
402 | next = ''
403 |
404 | if internal and request.method == 'GET' and fb.app_name:
405 | next = "%s%s" % (fb.get_app_url(), next)
406 |
407 | try:
408 | session_check = fb.check_session(request)
409 | except ValueError:
410 | session_check = False
411 |
412 | if session_check and required_permissions:
413 | req_perms = set(required_permissions)
414 | perms = set(fb.ext_perms)
415 | has_permissions = req_perms.issubset(perms)
416 | else:
417 | has_permissions = True
418 |
419 | if not (session_check and has_permissions):
420 | # If user has never logged in before, the get_login_url will
421 | # redirect to the TOS page
422 | return fb.redirect(
423 | fb.get_login_url(
424 | next=next,
425 | required_permissions=required_permissions))
426 |
427 | return view(request, *args, **kwargs)
428 | newview.next = next
429 | newview.internal = internal
430 | return newview
431 | return decorator
432 |
433 |
434 | def require_add(next=None, internal=None, on_install=None):
435 | """
436 | Decorator for Django views that requires application installation.
437 | The FacebookMiddleware must be installed.
438 |
439 | Standard usage:
440 | @require_add()
441 | def some_view(request):
442 | ...
443 |
444 | Redirecting after installation:
445 | To use the 'next' parameter to redirect to a specific page after login, a callable should
446 | return a path relative to the Post-add URL. 'next' can also be an integer specifying how many
447 | parts of request.path to strip to find the relative URL of the canvas page. If 'next' is None,
448 | settings.callback_path and settings.app_name are checked to redirect to the same page after logging
449 | in. (This is the default behavior.)
450 | @require_add(next=some_callable)
451 | def some_view(request):
452 | ...
453 |
454 | Post-install processing:
455 | Set the on_install parameter to a callable in order to handle special post-install processing.
456 | The callable should take a request object as the parameter.
457 | @require_add(on_install=some_callable)
458 | def some_view(request):
459 | ...
460 | """
461 | def decorator(view):
462 | def newview(request, *args, **kwargs):
463 | next = newview.next
464 | internal = newview.internal
465 |
466 | try:
467 | fb = request.facebook
468 | except:
469 | raise ImproperlyConfigured(
470 | 'Make sure you have the Facebook middleware installed.')
471 |
472 | if internal is None:
473 | internal = request.facebook.internal
474 |
475 | if callable(next):
476 | next = next(request.path)
477 | elif isinstance(next, int):
478 | next = '/'.join(request.path.split('/')[next + 1:])
479 | elif next is None and fb.callback_path and request.path.startswith(fb.callback_path):
480 | next = request.path[len(fb.callback_path):]
481 | else:
482 | next = ''
483 |
484 | if not fb.check_session(request):
485 | if fb.added:
486 | if request.method == 'GET' and fb.app_name:
487 | return fb.redirect('%s%s' % (fb.get_app_url(), next))
488 | return fb.redirect(fb.get_login_url(next=next))
489 | else:
490 | return fb.redirect(fb.get_add_url(next=next))
491 |
492 | if not fb.added:
493 | return fb.redirect(fb.get_add_url(next=next))
494 |
495 | if 'installed' in request.GET and callable(on_install):
496 | on_install(request)
497 |
498 | if internal and request.method == 'GET' and fb.app_name:
499 | return fb.redirect('%s%s' % (fb.get_app_url(), next))
500 |
501 | return view(request, *args, **kwargs)
502 | newview.next = next
503 | newview.internal = internal
504 | return newview
505 | return decorator
506 |
507 | # try to preserve the argspecs
508 | try:
509 | import decorator
510 | except ImportError:
511 | pass
512 | else:
513 | # Can this be done with functools.wraps, but maintaining kwargs?
514 | def updater(f):
515 | def updated(*args, **kwargs):
516 | original = f(*args, **kwargs)
517 |
518 | def newdecorator(view):
519 | return decorator.new_wrapper(original(view), view)
520 | return decorator.new_wrapper(newdecorator, original)
521 | return decorator.new_wrapper(updated, f)
522 | require_oauth = updater(require_oauth)
523 | process_oauth = updater(process_oauth)
524 | require_login = updater(require_login)
525 | require_add = updater(require_add)
526 |
527 |
528 | class FacebookMiddleware(object):
529 | """
530 | Middleware that attaches a Facebook object to every incoming request.
531 | The Facebook object created can also be accessed from models for the
532 | current thread by using get_facebook_client().
533 |
534 | callback_path can be a string or a callable. Using a callable lets us
535 | pass in something like lambda reverse('our_canvas_view') so we can follow
536 | the DRY principle.
537 |
538 | """
539 |
540 | def __init__(self, api_key=None, secret_key=None, app_name=None,
541 | callback_path=None, internal=None, app_id=None,
542 | oauth2=None, oauth2_redirect=None):
543 | self.api_key = api_key or settings.FACEBOOK_API_KEY
544 | self.secret_key = secret_key or settings.FACEBOOK_SECRET_KEY
545 | self.app_name = app_name or getattr(
546 | settings, 'FACEBOOK_APP_NAME', None)
547 | self.callback_path = callback_path or getattr(
548 | settings, 'FACEBOOK_CALLBACK_PATH', None)
549 | self.internal = internal or getattr(
550 | settings, 'FACEBOOK_INTERNAL', True)
551 | self.app_id = app_id or getattr(settings, 'FACEBOOK_APP_ID', None)
552 | self.oauth2 = oauth2 or getattr(settings, 'FACEBOOK_OAUTH2', False)
553 | self.oauth2_redirect = oauth2_redirect or getattr(
554 | settings, 'FACEBOOK_OAUTH2_REDIRECT', None)
555 | self.proxy = None
556 | if getattr(settings, 'USE_HTTP_PROXY', False):
557 | self.proxy = settings.HTTP_PROXY
558 |
559 | def process_request(self, request):
560 | callback_path = self.callback_path
561 | if callable(callback_path):
562 | callback_path = callback_path()
563 | _thread_locals.facebook = request.facebook = Facebook(
564 | self.api_key,
565 | self.secret_key,
566 | app_name=self.app_name,
567 | callback_path=callback_path,
568 | internal=self.internal,
569 | proxy=self.proxy,
570 | app_id=self.app_id,
571 | oauth2=self.oauth2)
572 | if self.oauth2:
573 | if self.oauth2_redirect:
574 | request.facebook.oauth2_redirect = self.oauth2_redirect
575 | request.facebook._oauth2_process_params(request)
576 | if not self.internal:
577 | if 'fb_sig_session_key' in request.GET and (
578 | 'fb_sig_user' in request.GET or 'fb_sig_canvas_user' in request.GET):
579 | request.facebook.session_key = request.session[
580 | 'facebook_session_key'] = request.GET['fb_sig_session_key']
581 | request.facebook.uid = request.session['facebook_user_id'] = request.GET[
582 | 'fb_sig_user'] or request.GET['fb_sig_canvas_user']
583 | elif int(request.GET.get('fb_sig_added', '1')) and request.session.get('facebook_session_key', None) \
584 | and request.session.get('facebook_user_id', None):
585 | request.facebook.session_key = request.session[
586 | 'facebook_session_key']
587 | request.facebook.uid = request.session['facebook_user_id']
588 |
589 | def process_response(self, request, response):
590 |
591 | # Don't assume that request.facebook exists
592 | # - it's not necessarily true that all process_requests will have been called
593 | try:
594 | fb = request.facebook
595 | except AttributeError:
596 | return response
597 |
598 | if not self.internal and fb.session_key and fb.uid:
599 | request.session['facebook_session_key'] = fb.session_key
600 | request.session['facebook_user_id'] = fb.uid
601 |
602 | if fb.session_key_expires:
603 | expiry = datetime.datetime.utcfromtimestamp(
604 | fb.session_key_expires)
605 | request.session.set_expiry(expiry)
606 |
607 | if not fb.is_session_from_cookie:
608 | # Make sure the browser accepts our session cookies inside an
609 | # Iframe
610 | response['P3P'] = 'CP="NOI DSP COR NID ADMa OPTa OUR NOR"'
611 | fb_cookies = {
612 | 'expires': fb.session_key_expires,
613 | 'session_key': fb.session_key,
614 | 'user': fb.uid,
615 | }
616 | fb_cookies = dict((k, v) for k, v in fb_cookies.items()
617 | if v is not None)
618 |
619 | expire_time = None
620 | if fb.session_key_expires:
621 | expire_time = datetime.datetime.utcfromtimestamp(
622 | fb.session_key_expires)
623 |
624 | for k in fb_cookies:
625 | response.set_cookie(
626 | self.api_key + '_' + k,
627 | fb_cookies[k],
628 | expires=expire_time)
629 | if fb_cookies:
630 | response.set_cookie(
631 | self.api_key,
632 | fb._hash_args(fb_cookies),
633 | expires=expire_time)
634 |
635 | return response
636 |
--------------------------------------------------------------------------------
/facebook/djangofb/context_processors.py:
--------------------------------------------------------------------------------
1 | def messages(request):
2 | """Returns messages similar to ``django.core.context_processors.auth``."""
3 | if hasattr(request, 'facebook') and request.facebook.uid is not None:
4 | from models import Message
5 | messages = Message.objects.get_and_delete_all(uid=request.facebook.uid)
6 | return {'messages': messages}
7 |
--------------------------------------------------------------------------------
/facebook/djangofb/default_app/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sciyoshi/pyfacebook/ec2bbfb0c687f2775ae89e1c5201688ed6e69b8e/facebook/djangofb/default_app/__init__.py
--------------------------------------------------------------------------------
/facebook/djangofb/default_app/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 | # get_facebook_client lets us get the current Facebook object
4 | # from outside of a view, which lets us have cleaner code
5 | from facebook.djangofb import get_facebook_client
6 |
7 |
8 | def _2int(d, k):
9 | try:
10 | d = d.__dict__
11 | except:
12 | pass
13 |
14 | t = d.get(k, '')
15 | if t == 'None':
16 | t = 0
17 | else:
18 | t = int(t)
19 | return t
20 |
21 |
22 | class UserManager(models.Manager):
23 | """Custom manager for a Facebook User."""
24 |
25 | def get_current(self):
26 | """Gets a User object for the logged-in Facebook user."""
27 | facebook = get_facebook_client()
28 | user, created = self.get_or_create(id=_2int(facebook, 'uid'))
29 | if created:
30 | # we could do some custom actions for new users here...
31 | pass
32 | return user
33 |
34 |
35 | class User(models.Model):
36 | """A simple User model for Facebook users."""
37 |
38 | # We use the user's UID as the primary key in our database.
39 | id = models.IntegerField(primary_key=True)
40 |
41 | # TODO: The data that you want to store for each user would go here.
42 | # For this sample, we let users let people know their favorite progamming
43 | # language, in the spirit of Extended Info.
44 | language = models.CharField(max_length=64, default='Python')
45 |
46 | # Add the custom manager
47 | objects = UserManager()
48 |
--------------------------------------------------------------------------------
/facebook/djangofb/default_app/templates/canvas.fbml:
--------------------------------------------------------------------------------
1 |
2 | {% comment %}
3 | We can use {{ fbuser }} to get at the current user.
4 | {{ fbuser.id }} will be the user's UID, and {{ fbuser.language }}
5 | is his/her favorite language (Python :-).
6 | {% endcomment %}
7 | Welcome, !
8 |
9 |
10 |
11 | Your favorite language is {{ fbuser.language|escape }}.
12 |
13 |
14 |
22 |
23 |
--------------------------------------------------------------------------------
/facebook/djangofb/default_app/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls.defaults import *
2 |
3 | urlpatterns = patterns('{{ project }}.{{ app }}.views',
4 | (r'^$', 'canvas'),
5 | # Define other pages you want to create here
6 | )
7 |
--------------------------------------------------------------------------------
/facebook/djangofb/default_app/views.py:
--------------------------------------------------------------------------------
1 | from django.http import HttpResponse
2 | from django.views.generic.simple import direct_to_template
3 | # uncomment the following two lines and the one below
4 | # if you dont want to use a decorator instead of the middleware
5 | # from django.utils.decorators import decorator_from_middleware
6 | # from facebook.djangofb import FacebookMiddleware
7 |
8 | # Import the Django helpers
9 | import facebook.djangofb as facebook
10 |
11 | # The User model defined in models.py
12 | from models import User
13 |
14 | # We'll require login for our canvas page. This
15 | # isn't necessarily a good idea, as we might want
16 | # to let users see the page without granting our app
17 | # access to their info. See the wiki for details on how
18 | # to do this.
19 | # @decorator_from_middleware(FacebookMiddleware)
20 |
21 |
22 | @facebook.require_login()
23 | def canvas(request):
24 | # Get the User object for the currently logged in user
25 | user = User.objects.get_current()
26 |
27 | # Check if we were POSTed the user's new language of choice
28 | if 'language' in request.POST:
29 | user.language = request.POST['language'][:64]
30 | user.save()
31 |
32 | # User is guaranteed to be logged in, so pass canvas.fbml
33 | # an extra 'fbuser' parameter that is the User object for
34 | # the currently logged in user.
35 | return direct_to_template(
36 | request,
37 | 'canvas.fbml',
38 | extra_context={
39 | 'fbuser': user})
40 |
41 |
42 | @facebook.require_login()
43 | def ajax(request):
44 | return HttpResponse('hello world')
45 |
--------------------------------------------------------------------------------
/facebook/djangofb/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from django.utils.html import escape
3 | from django.utils.safestring import mark_safe
4 |
5 | FB_MESSAGE_STATUS = (
6 | (0, 'Explanation'),
7 | (1, 'Error'),
8 | (2, 'Success'),
9 | )
10 |
11 |
12 | class MessageManager(models.Manager):
13 |
14 | def get_and_delete_all(self, uid):
15 | messages = []
16 | for m in self.filter(uid=uid):
17 | messages.append(m)
18 | m.delete()
19 | return messages
20 |
21 |
22 | class Message(models.Model):
23 | """Represents a message for a Facebook user."""
24 | uid = models.CharField(max_length=25)
25 | status = models.IntegerField(choices=FB_MESSAGE_STATUS)
26 | message = models.CharField(max_length=300)
27 | objects = MessageManager()
28 |
29 | def __unicode__(self):
30 | return self.message
31 |
32 | def _fb_tag(self):
33 | return self.get_status_display().lower()
34 |
35 | def as_fbml(self):
36 | return mark_safe(u'' % (
37 | self._fb_tag(),
38 | escape(self.message),
39 | ))
40 |
--------------------------------------------------------------------------------
/facebook/webappfb.py:
--------------------------------------------------------------------------------
1 | #
2 | # webappfb - Facebook tools for Google's AppEngine "webapp" Framework
3 | #
4 | # Copyright (c) 2009, Max Battcher
5 | # All rights reserved.
6 | #
7 | # Redistribution and use in source and binary forms, with or without
8 | # modification, are permitted provided that the following conditions are met:
9 | # * Redistributions of source code must retain the above copyright
10 | # notice, this list of conditions and the following disclaimer.
11 | # * Redistributions in binary form must reproduce the above copyright
12 | # notice, this list of conditions and the following disclaimer in the
13 | # documentation and/or other materials provided with the distribution.
14 | # * Neither the name of the author nor the names of its contributors may
15 | # be used to endorse or promote products derived from this software
16 | # without specific prior written permission.
17 | #
18 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS``AS IS'' AND ANY
19 | # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | # DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
22 | # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 | from google.appengine.api import memcache
29 | from google.appengine.ext.webapp import RequestHandler
30 | from facebook import Facebook
31 | import yaml
32 |
33 | """
34 | Facebook tools for Google AppEngine's object-oriented "webapp" framework.
35 | """
36 |
37 | # This global configuration dictionary is for configuration variables
38 | # for Facebook requests such as the application's API key and secret
39 | # key. Defaults to loading a 'facebook.yaml' YAML file. This should be
40 | # useful and familiar for most AppEngine development.
41 | FACEBOOK_CONFIG = yaml.load(file('facebook.yaml', 'r'))
42 |
43 |
44 | class FacebookRequestHandler(RequestHandler):
45 | """
46 | Base class for request handlers for Facebook apps, providing useful
47 | Facebook-related tools: a local
48 | """
49 |
50 | def _fbconfig_value(self, name, default=None):
51 | """
52 | Checks the global config dictionary and then for a class/instance
53 | variable, using a provided default if no value is found.
54 | """
55 | if name in FACEBOOK_CONFIG:
56 | default = FACEBOOK_CONFIG[name]
57 |
58 | return getattr(self, name, default)
59 |
60 | def initialize(self, request, response):
61 | """
62 | Initialize's this request's Facebook client.
63 | """
64 | super(FacebookRequestHandler, self).initialize(request, response)
65 |
66 | app_name = self._fbconfig_value('app_name', '')
67 | api_key = self._fbconfig_value('api_key', None)
68 | secret_key = self._fbconfig_value('secret_key', None)
69 |
70 | self.facebook = Facebook(api_key, secret_key,
71 | app_name=app_name)
72 |
73 | require_app = self._fbconfig_value('require_app', False)
74 | require_login = self._fbconfig_value('require_login', False)
75 | need_session = self._fbconfig_value('need_session', False)
76 | check_session = self._fbconfig_value('check_session', True)
77 |
78 | self._messages = None
79 | self.redirecting = False
80 |
81 | if require_app or require_login:
82 | if not self.facebook.check_session(request):
83 | self.redirect(self.facebook.get_login_url(next=request.url))
84 | self.redirecting = True
85 | return
86 | elif check_session:
87 | self.facebook.check_session(request) # ignore response
88 |
89 | # NOTE: require_app is deprecated according to modern Facebook login
90 | # policies. Included for completeness, but unnecessary.
91 | if require_app and not self.facebook.added:
92 | self.redirect(self.facebook.get_add_url(next=request.url))
93 | self.redirecting = True
94 | return
95 |
96 | if not (require_app or require_login) and need_session:
97 | self.facebook.auth.getSession()
98 |
99 | def redirect(self, url, **kwargs):
100 | """
101 | For Facebook canvas pages we should use instead of
102 | a normal redirect.
103 | """
104 | if self.facebook.in_canvas:
105 | self.response.clear()
106 | self.response.out.write('' % (url, ))
107 | else:
108 | super(FacebookRequestHandler, self).redirect(url, **kwargs)
109 |
110 | def add_user_message(self, kind, msg, detail='', time=15 * 60):
111 | """
112 | Add a message to the current user to memcache.
113 | """
114 | if self.facebook.uid:
115 | key = 'messages:%s' % self.facebook.uid
116 | self._messages = memcache.get(key)
117 | message = {
118 | 'kind': kind,
119 | 'message': msg,
120 | 'detail': detail,
121 | }
122 | if self._messages is not None:
123 | self._messages.append(message)
124 | else:
125 | self._messages = [message]
126 | memcache.set(key, self._messages, time=time)
127 |
128 | def get_and_delete_user_messages(self):
129 | """
130 | Get all of the messages for the current user; removing them.
131 | """
132 | if self.facebook.uid:
133 | key = 'messages:%s' % self.facebook.uid
134 | if not hasattr(self, '_messages') or self._messages is None:
135 | self._messages = memcache.get(key)
136 | memcache.delete(key)
137 | return self._messages
138 | return None
139 |
140 |
141 | class FacebookCanvasHandler(FacebookRequestHandler):
142 | """
143 | Request handler for Facebook canvas (FBML application) requests.
144 | """
145 |
146 | def canvas(self, *args, **kwargs):
147 | """
148 | This will be your handler to deal with Canvas requests.
149 | """
150 | raise NotImplementedError()
151 |
152 | def get(self, *args):
153 | """
154 | All valid canvas views are POSTS.
155 | """
156 | # TODO: Attempt to auto-redirect to Facebook canvas?
157 | self.error(404)
158 |
159 | def post(self, *args, **kwargs):
160 | """
161 | Check a couple of simple safety checks and then call the canvas
162 | handler.
163 | """
164 | if self.redirecting:
165 | return
166 |
167 | if not self.facebook.in_canvas:
168 | self.error(404)
169 | return
170 |
171 | self.canvas(*args, **kwargs)
172 |
173 | # vim: ai et ts=4 sts=4 sw=4
174 |
--------------------------------------------------------------------------------
/facebook/wsgi.py:
--------------------------------------------------------------------------------
1 | """This is some simple helper code to bridge the Pylons / PyFacebook gap.
2 |
3 | There's some generic WSGI middleware, some Paste stuff, and some Pylons
4 | stuff. Once you put FacebookWSGIMiddleware into your middleware stack,
5 | you'll have access to ``environ["pyfacebook.facebook"]``, which is a
6 | ``facebook.Facebook`` object. If you're using Paste (which includes
7 | Pylons users), you can also access this directly using the facebook
8 | global in this module.
9 |
10 | """
11 |
12 | # Be careful what you import. Don't expect everyone to have Pylons,
13 | # Paste, etc. installed. Degrade gracefully.
14 |
15 | from facebook import Facebook
16 |
17 | __docformat__ = "restructuredtext"
18 |
19 |
20 | # Setup Paste, if available. This needs to stay in the same module as
21 | # FacebookWSGIMiddleware below.
22 |
23 | try:
24 | from paste.registry import StackedObjectProxy
25 | from webob.exc import _HTTPMove
26 | try:
27 | from string import Template
28 | except ImportError:
29 | from webob.util.stringtemplate import Template
30 | from webob import html_escape
31 |
32 | except ImportError:
33 | pass
34 | else:
35 | facebook = StackedObjectProxy(name="PyFacebook Facebook Connection")
36 |
37 | class CanvasRedirect(_HTTPMove):
38 | """This is for canvas redirects."""
39 |
40 | title = "See Other"
41 | code = 200
42 | html_template_obj = Template('')
43 |
44 | def html_body(self, environ):
45 | return self.html_template_obj.substitute(location=self.detail)
46 |
47 |
48 | class FacebookWSGIMiddleware(object):
49 |
50 | """This is WSGI middleware for Facebook."""
51 |
52 | def __init__(self, app, config, facebook_class=Facebook):
53 | """Initialize the Facebook middleware.
54 |
55 | ``app``
56 | This is the WSGI application being wrapped.
57 |
58 | ``config``
59 | This is a dict containing the keys "pyfacebook.apikey" and
60 | "pyfacebook.secret".
61 |
62 | ``facebook_class``
63 | If you want to subclass the Facebook class, you can pass in
64 | your replacement here. Pylons users will want to use
65 | PylonsFacebook.
66 |
67 | """
68 | self.app = app
69 | self.config = config
70 | self.facebook_class = facebook_class
71 |
72 | def __call__(self, environ, start_response):
73 | config = self.config
74 | real_facebook = self.facebook_class(config["pyfacebook.apikey"],
75 | config["pyfacebook.secret"])
76 | registry = environ.get('paste.registry')
77 | if registry:
78 | registry.register(facebook, real_facebook)
79 | environ['pyfacebook.facebook'] = real_facebook
80 | return self.app(environ, start_response)
81 |
82 |
83 | # The remainder is Pylons specific.
84 |
85 | try:
86 | import pylons
87 | from pylons.controllers.util import redirect_to as pylons_redirect_to
88 | from routes import url_for
89 | except ImportError:
90 | pass
91 | else:
92 |
93 | class PylonsFacebook(Facebook):
94 |
95 | """Subclass Facebook to add Pylons goodies."""
96 |
97 | def check_session(self, request=None):
98 | """The request parameter is now optional."""
99 | if request is None:
100 | request = pylons.request
101 | return Facebook.check_session(self, request)
102 |
103 | # The Django request object is similar enough to the Paste
104 | # request object that check_session and validate_signature
105 | # should *just work*.
106 |
107 | def redirect_to(self, url):
108 | """Wrap Pylons' redirect_to function so that it works in_canvas.
109 |
110 | By the way, this won't work until after you call
111 | check_session().
112 |
113 | """
114 | if self.in_canvas:
115 | raise CanvasRedirect(url)
116 | pylons_redirect_to(url)
117 |
118 | def apps_url_for(self, *args, **kargs):
119 | """Like url_for, but starts with "http://apps.facebook.com"."""
120 | return "http://apps.facebook.com" + url_for(*args, **kargs)
121 |
122 | def create_pylons_facebook_middleware(app, config):
123 | """This is a simple wrapper for FacebookWSGIMiddleware.
124 |
125 | It passes the correct facebook_class.
126 |
127 | """
128 | return FacebookWSGIMiddleware(app, config,
129 | facebook_class=PylonsFacebook)
130 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | from setuptools import setup, find_packages
4 |
5 | setup(name='pyfacebook',
6 | version='1.0a2.1',
7 | description='Python Client Library for the Facebook API',
8 | author='Samuel Cormier-Iijima',
9 | author_email='sciyoshi@gmail.com',
10 | url='http://code.google.com/p/pyfacebook',
11 | packages=['facebook',
12 | 'facebook.djangofb',
13 | 'facebook.djangofb.default_app'],
14 | test_suite='tests',
15 | tests_require=['MiniMock'])
16 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sciyoshi/pyfacebook/ec2bbfb0c687f2775ae89e1c5201688ed6e69b8e/tests/__init__.py
--------------------------------------------------------------------------------
/tests/test.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import sys
3 | import os
4 | import facebook
5 | import urllib2
6 | try:
7 | from hashlib import md5
8 | md5_constructor = md5
9 | except ImportError:
10 | import md5
11 | md5_constructor = md5.new
12 | try:
13 | import simplejson
14 | except ImportError:
15 | from django.utils import simplejson
16 | import httplib
17 | from minimock import Mock
18 |
19 | my_api_key = "e1e9cfeb5e0d7a52e4fbd5d09e1b873e"
20 | my_secret_key = "1bebae7283f5b79aaf9b851addd55b90"
21 | # '{"error_code":100,\
22 | # "error_msg":"Invalid parameter",\
23 | # "request_args":[{"key":"format","value":"JSON"},\
24 | # {"key":"auth_token","value":"24626e24bb12919f2f142145070542e8"},\
25 | # {"key":"sig","value":"36af2af3b93da784149301e77cb1621a"},\
26 | # {"key":"v","value":"1.0"},\
27 | # {"key":"api_key","value":"e1e9cfeb5e0d7a52e4fbd5d09e1b873e"},\
28 | # {"key":"method","value":"facebook.auth.getSession"}]}'
29 | response_str = '{"stuff":"abcd"}'
30 |
31 |
32 | class MyUrlOpen:
33 |
34 | def __init__(self, *args, **kwargs):
35 | pass
36 |
37 | def read(self):
38 | global response_str
39 | return response_str
40 |
41 |
42 | class pyfacebook_UnitTests(unittest.TestCase):
43 |
44 | def setUp(self):
45 | facebook.urllib2.urlopen = Mock('urllib2.urlopen')
46 | facebook.urllib2.urlopen.mock_returns_func = MyUrlOpen
47 | pass
48 |
49 | def tearDown(self):
50 | pass
51 |
52 | def login(self):
53 | pass
54 |
55 | def test1(self):
56 | f = facebook.Facebook(api_key=my_api_key, secret_key=my_secret_key)
57 | f.login = self.login
58 | self.assertEquals(f.api_key, my_api_key)
59 | self.assertEquals(f.secret_key, my_secret_key)
60 | self.assertEquals(f.auth_token, None)
61 | self.assertEquals(f.app_name, None)
62 | self.assertEquals(f.callback_path, None)
63 | self.assertEquals(f.internal, None)
64 |
65 | def test2(self):
66 | args = {"arg1": "a", "arg2": "b", "arg3": "c"}
67 | hasher = md5_constructor(
68 | ''.join(['%s=%s' % (x, args[x]) for x in sorted(args.keys())]))
69 | hasher.update("acdnj")
70 | f = facebook.Facebook(api_key="abcdf", secret_key="acdnj")
71 | f.login = self.login
72 | digest = f._hash_args(args)
73 | self.assertEquals(hasher.hexdigest(), digest)
74 | hasher = md5_constructor(
75 | ''.join(['%s=%s' % (x, args[x]) for x in sorted(args.keys())]))
76 | hasher.update("klmn")
77 | # trunk code has error hash.updated instead of hash.update
78 | digest = f._hash_args(args, secret="klmn")
79 | self.assertEquals(hasher.hexdigest(), digest)
80 |
81 | hasher = md5_constructor(
82 | ''.join(['%s=%s' % (x, args[x]) for x in sorted(args.keys())]))
83 | f.secret = "klmn"
84 | hasher.update(f.secret)
85 | # trunk code has error hash.updated instead of hash.update
86 | digest = f._hash_args(args)
87 | self.assertEquals(hasher.hexdigest(), digest)
88 |
89 | def test3(self):
90 | global response_str
91 | response = {'stuff': 'abcd'}
92 | response_str = simplejson.dumps(response)
93 | fb = facebook.Facebook(my_api_key, my_secret_key)
94 | fb.login = self.login
95 | fb.auth.createToken()
96 | self.assertEquals(str(fb.auth_token['stuff']), "abcd")
97 | fb.login()
98 | response = {
99 | "session_key": "key",
100 | "uid": "my_uid",
101 | "secret": "my_secret",
102 | "expires": "my_expires"}
103 | response_str = simplejson.dumps(response)
104 | res = fb.auth.getSession()
105 | self.assertEquals(str(res["expires"]), response["expires"])
106 | self.assertEquals(str(res["secret"]), response["secret"])
107 | self.assertEquals(str(res["session_key"]), response["session_key"])
108 | self.assertEquals(str(res["uid"]), response["uid"])
109 |
110 | def test4(self):
111 | global response_str
112 | response = 'abcdef'
113 | response_str = simplejson.dumps(response)
114 | fb = facebook.Facebook(my_api_key, my_secret_key)
115 | fb.login = self.login
116 | fb.auth.createToken()
117 | self.assertEquals(str(fb.auth_token), "abcdef")
118 | url = fb.get_login_url(next="nowhere", popup=True, canvas=True)
119 | self.assertEquals(
120 | url,
121 | 'http://www.facebook.com/login.php?canvas=1&popup=1&auth_token=abcdef&next=nowhere&v=1.0&api_key=%s' %
122 | (my_api_key,
123 | ))
124 |
125 | def test5(self):
126 | class Request:
127 |
128 | def __init__(self, post, get, method):
129 | self.POST = post
130 | self.GET = get
131 | self.method = method
132 |
133 | req = Request({'fb_sig_in_canvas': 1}, {}, 'POST')
134 | fb = facebook.Facebook(my_api_key, my_secret_key)
135 | fb.login = self.login
136 | res = fb.check_session(req)
137 | self.assertFalse(res)
138 | req = Request({'fb_sig': 1}, {}, 'POST')
139 | res = fb.check_session(req)
140 | self.assertFalse(res)
141 | req = Request({'fb_sig': fb._hash_args({'in_canvas': '1',
142 | 'added': '1',
143 | 'expires': '1',
144 | 'friends': 'joe,mary',
145 | 'session_key': 'abc',
146 | 'user': 'bob'}),
147 | 'fb_sig_in_canvas': '1',
148 | 'fb_sig_added': '1',
149 | 'fb_sig_expires': '1',
150 | 'fb_sig_friends': 'joe,mary',
151 | 'fb_sig_session_key': 'abc',
152 | 'fb_sig_user': 'bob'},
153 | {}, 'POST')
154 | res = fb.check_session(req)
155 | self.assertTrue(res)
156 | fb = facebook.Facebook(my_api_key, my_secret_key)
157 | fb.login = self.login
158 | req = Request({'fb_sig': fb._hash_args({'in_canvas': '1',
159 | 'added': '1',
160 | 'expires': '1',
161 | 'friends': '',
162 | 'session_key': 'abc',
163 | 'user': 'bob'}),
164 | 'fb_sig_in_canvas': '1',
165 | 'fb_sig_added': '1',
166 | 'fb_sig_expires': '1',
167 | 'fb_sig_friends': '',
168 | 'fb_sig_session_key': 'abc',
169 | 'fb_sig_user': 'bob'},
170 | {}, 'POST')
171 | res = fb.check_session(req)
172 | self.assertTrue(res)
173 | fb = facebook.Facebook(my_api_key, my_secret_key)
174 | fb.login = self.login
175 | req = Request({'fb_sig': fb._hash_args({'in_canvas': '1',
176 | 'added': '1',
177 | 'expires': '1',
178 | 'friends': '',
179 | 'session_key': 'abc',
180 | 'page_id': 'id'}),
181 | 'fb_sig_in_canvas': '1',
182 | 'fb_sig_added': '1',
183 | 'fb_sig_expires': '1',
184 | 'fb_sig_friends': '',
185 | 'fb_sig_session_key': 'abc',
186 | 'fb_sig_page_id': 'id'},
187 | {}, 'POST')
188 | res = fb.check_session(req)
189 | self.assertTrue(res)
190 |
191 | def test6(self):
192 | global response_str
193 | response = 'abcdef'
194 | response_str = simplejson.dumps(response)
195 | fb = facebook.Facebook(my_api_key, my_secret_key)
196 | fb.login = self.login
197 | fb.auth.createToken()
198 | # self.failUnlessRaises(RuntimeError,fb._add_session_args)
199 | response = {
200 | "session_key": "key",
201 | "uid": "my_uid",
202 | "secret": "my_secret",
203 | "expires": "my_expires"}
204 | response_str = simplejson.dumps(response)
205 | fb.auth.getSession()
206 | args = fb._add_session_args()
207 |
208 | def test7(self):
209 | global response_str
210 | response = 'abcdef'
211 | response_str = simplejson.dumps(response)
212 | fb = facebook.Facebook(my_api_key, my_secret_key)
213 | fb.login = self.login
214 | fb.auth.createToken()
215 | self.assertEquals(str(fb.auth_token), "abcdef")
216 | url = fb.get_authorize_url(next="next", next_cancel="next_cancel")
217 | self.assertEquals(
218 | url,
219 | 'http://www.facebook.com/authorize.php?api_key=%s&next_cancel=next_cancel&v=1.0&next=next' %
220 | (my_api_key,
221 | ))
222 |
223 | def test8(self):
224 | class Request:
225 |
226 | def __init__(self, post, get, method):
227 | self.POST = post
228 | self.GET = get
229 | self.method = method
230 |
231 | global response_str
232 | response = {
233 | "session_key": "abcdef",
234 | "uid": "my_uid",
235 | "secret": "my_secret",
236 | "expires": "my_expires"}
237 | response_str = simplejson.dumps(response)
238 | req = Request({}, {'installed': 1, 'fb_page_id': 'id',
239 | 'auth_token': 'abcdef'}, 'GET')
240 | fb = facebook.Facebook(my_api_key, my_secret_key)
241 | fb.login = self.login
242 | res = fb.check_session(req)
243 | self.assertTrue(res)
244 |
245 | def test9(self):
246 | global response_str
247 | response = 'abcdef'
248 | response_str = simplejson.dumps(response)
249 | fb = facebook.Facebook(my_api_key, my_secret_key)
250 | fb.login = self.login
251 | fb.auth.createToken()
252 | self.assertEquals(str(fb.auth_token), "abcdef")
253 | url = fb.get_add_url(next="next")
254 | self.assertEquals(
255 | url, 'http://www.facebook.com/install.php?api_key=%s&v=1.0&next=next' %
256 | (my_api_key,))
257 |
258 | def send(self, xml):
259 | self.xml = xml
260 |
261 | def test10(self):
262 | import Image
263 | image1 = Image.new("RGB", (400, 300), (255, 255, 255))
264 | filename = "image_file.jpg"
265 | image1.save(filename)
266 | global response_str
267 | fb = facebook.Facebook(my_api_key, my_secret_key)
268 | fb.login = self.login
269 |
270 | facebook.httplib.HTTP = Mock('httplib.HTTP')
271 | http_connection = Mock('http_connection')
272 | facebook.httplib.HTTP.mock_returns = http_connection
273 | http_connection.send.mock_returns_func = self.send
274 |
275 | def _http_passes():
276 | return [200, ]
277 | http_connection.getreply.mock_returns_func = _http_passes
278 |
279 | def read():
280 | response = {"stuff": "stuff"}
281 | response_str = simplejson.dumps(response)
282 | return response_str
283 | http_connection.file.read.mock_returns_func = read
284 |
285 | response = {
286 | "session_key": "key",
287 | "uid": "my_uid",
288 | "secret": "my_secret",
289 | "expires": "my_expires"}
290 | response_str = simplejson.dumps(response)
291 | res = fb.auth.getSession()
292 | result = fb.photos.upload(
293 | image=filename,
294 | aid="aid",
295 | caption="a caption")
296 | self.assertEquals(str(result["stuff"]), "stuff")
297 | os.remove(filename)
298 |
299 | if __name__ == "__main__":
300 |
301 | # Build the test suite
302 | suite = unittest.TestSuite()
303 | suite.addTest(unittest.makeSuite(pyfacebook_UnitTests))
304 |
305 | # Execute the test suite
306 | print("Testing Proxy class\n")
307 | result = unittest.TextTestRunner(verbosity=2).run(suite)
308 | sys.exit(len(result.errors) + len(result.failures))
309 |
--------------------------------------------------------------------------------