├── examples
├── pyfacebook_sample
│ ├── __init__.py
│ ├── models.py
│ ├── templates
│ │ └── facebook
│ │ │ └── canvas.fbml
│ ├── urls.py
│ ├── README
│ └── views.py
└── examples.py
├── facebook
├── djangofb
│ ├── default_app
│ │ ├── __init__.py
│ │ ├── urls.py
│ │ ├── templates
│ │ │ └── canvas.fbml
│ │ ├── views.py
│ │ └── models.py
│ ├── context_processors.py
│ ├── models.py
│ └── __init__.py
├── wsgi.py
└── __init__.py
├── MANIFEST.in
├── setup.py
├── README
├── posediff
├── bin
└── djangofb.py
└── tests
├── minimock.py
└── test.py
/examples/pyfacebook_sample/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/pyfacebook_sample/models.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/facebook/djangofb/default_app/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | recursive-include examples *
2 |
--------------------------------------------------------------------------------
/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 |
8 |
--------------------------------------------------------------------------------
/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}
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | from distutils.core import setup
4 |
5 | setup(name='pyfacebook',
6 | version='0.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', 'facebook.djangofb'])
12 |
--------------------------------------------------------------------------------
/examples/pyfacebook_sample/templates/facebook/canvas.fbml:
--------------------------------------------------------------------------------
1 |
2 | Welcome, {{ name }}!
3 |
4 |
5 |
6 | This is the sample PyFaceBook application.
7 |
8 |
9 |
18 |
19 |
--------------------------------------------------------------------------------
/examples/pyfacebook_sample/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls.defaults import *
2 |
3 | # Hack to get the project name
4 | project = __name__.split('.')[0]
5 |
6 | # You'd want to change this to wherever your app lives
7 | urlpatterns = patterns(project + '.pyfacebook_sample.views',
8 | # Some functionality - users can post text to their homepage
9 | (r'^canvas/post/', 'post'),
10 |
11 | # For the mock AJAX functionality
12 | (r'^canvas/ajax/', 'ajax'),
13 |
14 | # This is the canvas callback, i.e. what will be seen
15 | # when you visit http://apps.facebook.com/.
16 | (r'^canvas/', 'canvas'),
17 |
18 | # Extra callbacks can be set in the Facebook app settings
19 | # page. For example, post_add will be called when a user
20 | # has added the application.
21 | (r'^post_add/', 'post_add'),
22 |
23 | )
24 |
--------------------------------------------------------------------------------
/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/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 | class MessageManager(models.Manager):
12 | def get_and_delete_all(self, uid):
13 | messages = []
14 | for m in self.filter(uid=uid):
15 | messages.append(m)
16 | m.delete()
17 | return messages
18 |
19 | class Message(models.Model):
20 | """Represents a message for a Facebook user."""
21 | uid = models.CharField(max_length=25)
22 | status = models.IntegerField(choices=FB_MESSAGE_STATUS)
23 | message = models.CharField(max_length=300)
24 | objects = MessageManager()
25 |
26 | def __unicode__(self):
27 | return self.message
28 |
29 | def _fb_tag(self):
30 | return self.get_status_display().lower()
31 |
32 | def as_fbml(self):
33 | return mark_safe(u'' % (
34 | self._fb_tag(),
35 | escape(self.message),
36 | ))
37 |
--------------------------------------------------------------------------------
/facebook/djangofb/default_app/views.py:
--------------------------------------------------------------------------------
1 | from django.http import HttpResponse
2 | from django.views.generic.simple import direct_to_template
3 |
4 | # Import the Django helpers
5 | import facebook.djangofb as facebook
6 |
7 | # The User model defined in models.py
8 | from models import User
9 |
10 | # We'll require login for our canvas page. This
11 | # isn't necessarily a good idea, as we might want
12 | # to let users see the page without granting our app
13 | # access to their info. See the wiki for details on how
14 | # to do this.
15 | @facebook.require_login()
16 | def canvas(request):
17 | # Get the User object for the currently logged in user
18 | user = User.objects.get_current()
19 |
20 | # Check if we were POSTed the user's new language of choice
21 | if 'language' in request.POST:
22 | user.language = request.POST['language'][:64]
23 | user.save()
24 |
25 | # User is guaranteed to be logged in, so pass canvas.fbml
26 | # an extra 'fbuser' parameter that is the User object for
27 | # the currently logged in user.
28 | return direct_to_template(request, 'canvas.fbml', extra_context={'fbuser': user})
29 |
30 | @facebook.require_login()
31 | def ajax(request):
32 | return HttpResponse('hello world')
33 |
--------------------------------------------------------------------------------
/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 | class UserManager(models.Manager):
8 | """Custom manager for a Facebook User."""
9 |
10 | def get_current(self):
11 | """Gets a User object for the logged-in Facebook user."""
12 | facebook = get_facebook_client()
13 | user, created = self.get_or_create(id=int(facebook.uid))
14 | if created:
15 | # we could do some custom actions for new users here...
16 | pass
17 | return user
18 |
19 | class User(models.Model):
20 | """A simple User model for Facebook users."""
21 |
22 | # We use the user's UID as the primary key in our database.
23 | id = models.IntegerField(primary_key=True)
24 |
25 | # TODO: The data that you want to store for each user would go here.
26 | # For this sample, we let users let people know their favorite progamming
27 | # language, in the spirit of Extended Info.
28 | language = models.CharField(maxlength=64, default='Python')
29 |
30 | # Add the custom manager
31 | objects = UserManager()
32 |
--------------------------------------------------------------------------------
/examples/pyfacebook_sample/README:
--------------------------------------------------------------------------------
1 | This is a sample Django application for PyFacebook.
2 |
3 | To use this application, copy the entire folder to an existing
4 | Django application. Then you need to edit the following settings:
5 |
6 | * In settings.py, make sure that TEMPLATE_LOADERS contains
7 | 'django.template.loaders.app_directories.load_template_source'. This
8 | is so that templates can be loaded from this template directory.
9 |
10 | * In settings.py, define FACEBOOK_API_KEY and FACEBOOK_SECRET_KEY to your
11 | own values.
12 |
13 | * In settings.py, add the following line to the variable MIDDLEWARE_CLASSES:
14 | 'facebook.djangofb.FacebookMiddleware'. This will attach a facebook object
15 | to every incoming request.
16 |
17 | * In urls.py, have something in your urlpatterns like:
18 | (r'^facebook/', include('YOUR_PROJECT_NAME.pyfacebook_sample.urls')),
19 | This will tell the sample application to live under /facebook/.
20 |
21 | * On the Facebook applications page, make sure that you set your callback
22 | to the appropriate URL. In this example, it would be
23 | 'http://YOUR_IP/facebook/canvas/', and DON'T FORGET THE TRAILING SLASH :-)
24 |
25 | * Change any occurrences of pyfacebook to your own application name.
26 |
27 | That should be about it...
28 |
--------------------------------------------------------------------------------
/examples/pyfacebook_sample/views.py:
--------------------------------------------------------------------------------
1 | from django.http import HttpResponse, HttpResponseRedirect
2 | from django.shortcuts import render_to_response
3 |
4 | import facebook.djangofb as facebook
5 |
6 | @facebook.require_login()
7 | def canvas(request):
8 | # If you're using FBML, it'd be better to use than to do this - this is just as an example
9 | values = request.facebook.users.getInfo([request.facebook.uid], ['first_name', 'is_app_user', 'has_added_app'])[0]
10 |
11 | name, is_app_user, has_added_app = values['first_name'], values['is_app_user'], values['has_added_app']
12 |
13 | if has_added_app == '0':
14 | return request.facebook.redirect(request.facebook.get_add_url())
15 |
16 | return render_to_response('facebook/canvas.fbml', {'name': name})
17 |
18 | @facebook.require_login()
19 | def post(request):
20 | request.facebook.profile.setFBML(request.POST['profile_text'], request.facebook.uid)
21 |
22 | return request.facebook.redirect(request.facebook.get_url('profile', id=request.facebook.uid))
23 |
24 | @facebook.require_login()
25 | def post_add(request):
26 | request.facebook.profile.setFBML(uid=request.facebook.uid, profile='Congratulations on adding PyFaceBook. Please click on the PyFaceBook link on the left side to change this text.')
27 |
28 | return request.facebook.redirect('http://apps.facebook.com/pyfacebook/')
29 |
30 | @facebook.require_login()
31 | def ajax(request):
32 | return HttpResponse('hello world')
33 |
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 | == PyFacebook ==
2 |
3 | PyFacebook is a Python client library for the Facebook API.
4 |
5 | Samuel Cormier-Iijima (sciyoshi@gmail.com)
6 | Niran Babalola (iamniran@gmail.com)
7 | Shannon -jj Behrens (jjinux@gmail.com)
8 | David B. Edelstein (David.B.Edelstein@gmail.com)
9 | Max Battcher (max.battcher@gmail.com)
10 | Rohan Deshpande (rohan.deshpande@gmail.com)
11 | Matthew Stevens (matthew.stevens@gmail.com)
12 | Sandro Turriate (sandro.turriate@gmail.com)
13 | Benjamin Zimmerman (benjamin.zimmerman@gmail.com)
14 | Gisle Aas (gisle.aas@gmail.com)
15 | Rand Bradley (rand.bradley@gmail.com)
16 | Luke Worth (luke.worth@gmail.com)
17 |
18 | http://pyfacebook.googlecode.com/
19 |
20 |
21 | == Usage ==
22 |
23 | To use this in your own projects, do the standard:
24 |
25 | python setup.py install
26 |
27 |
28 | == Documentation ==
29 |
30 | Have a look at the examples/ directory. Most of the stuff should be
31 | self-explanatory. There is also an example Django app in
32 | examples/facebook, which should have enough to get you started.
33 |
34 | See the developer wiki for more information.
35 |
36 |
37 | == License ==
38 |
39 | Copyright (c) 2008, Samuel Cormier-Iijima
40 | All rights reserved.
41 |
42 | Redistribution and use in source and binary forms, with or without
43 | modification, are permitted provided that the following conditions are met:
44 | * Redistributions of source code must retain the above copyright
45 | notice, this list of conditions and the following disclaimer.
46 | * Redistributions in binary form must reproduce the above copyright
47 | notice, this list of conditions and the following disclaimer in the
48 | documentation and/or other materials provided with the distribution.
49 | * Neither the name of the author nor the names of its contributors may
50 | be used to endorse or promote products derived from this software
51 | without specific prior written permission.
52 |
53 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS``AS IS'' AND ANY
54 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
55 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
56 | DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
57 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
58 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
59 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
60 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
61 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
62 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
63 |
64 |
--------------------------------------------------------------------------------
/examples/examples.py:
--------------------------------------------------------------------------------
1 | # -----------------------
2 | # Web application example
3 | # -----------------------
4 |
5 | def simple_web_app(request, api_key, secret_key):
6 | fb = Facebook(api_key, secret_key, request.GET['auth_token'])
7 | fb.auth.getSession()
8 |
9 | friend_ids = fb.friends.get()
10 | info = fb.users.getInfo(friend_ids, ['name', 'pic'])
11 |
12 | print ''
13 | for friend in info:
14 | print '%(name)s' % friend
15 | print ''
16 |
17 | def web_app(request):
18 | """Get the user's friends and their pictures. This example uses
19 | the Django web framework, but should be adaptable to others."""
20 |
21 | # Get api_key and secret_key from a file
22 | fb_file = open('facebook_keys.txt').readlines()
23 | api_key = fb_file[0].strip()
24 | secret_key = fb_file[1].strip()
25 | fb = Facebook(api_key, secret_key)
26 |
27 | # Use the data from the cookie if present
28 | if 'session_key' in request.session and 'uid' in request.session:
29 | fb.session_key = request.session['session_key']
30 | fb.uid = request.session['uid']
31 | else:
32 |
33 | try:
34 | fb.auth_token = request.GET['auth_token']
35 | except KeyError:
36 | # Send user to the Facebook to login
37 | return HttpResponseRedirect(fb.get_login_url())
38 |
39 | # getSession sets the session_key and uid
40 | # Store these in the cookie so we don't have to get them again
41 | fb.auth.getSession()
42 | request.session['session_key'] = fb.session_key
43 | request.session['uid'] = fb.uid
44 |
45 | try:
46 | friend_ids = fb.friends.get()
47 | except FacebookError, e:
48 | # Error 102 means the session has expired.
49 | # Delete the cookie and send the user to Facebook to login
50 | if e.info['code'] == u'102':
51 | del request.session['session_key']
52 | del request.session['uid']
53 | return HttpResponseRedirect(fb.get_login_url())
54 | else:
55 | # Other Facebook errors are possible too. Don't ignore them.
56 | raise
57 |
58 | info = fb.users.getInfo(friend_ids, ['name', 'pic'])
59 | # info is a list of dictionaries
60 |
61 | # you would never do this in an actual Django application,
62 | # it's just an example of accessing the results.
63 | links = []
64 | for friend in info:
65 | html = '%(name)s' % friend
66 | links.append(html)
67 |
68 | return render_to_response('template.html', {'links': links})
69 |
70 | # ---------------------------
71 | # Desktop application example
72 | # ---------------------------
73 |
74 | def desktop_app():
75 | from facebook import Facebook
76 |
77 | # Get api_key and secret_key from a file
78 | fbs = open(FB_SETTINGS).readlines()
79 | facebook = Facebook(fbs[0].strip(), fbs[1].strip())
80 |
81 | facebook.auth.createToken()
82 | # Show login window
83 | facebook.login()
84 |
85 | # Login to the window, then press enter
86 | print 'After logging in, press enter...'
87 | raw_input()
88 |
89 | facebook.auth.getSession()
90 | info = facebook.users.getInfo([facebook.uid], ['name', 'birthday', 'affiliations', 'sex'])[0]
91 |
92 | for attr in info:
93 | print '%s: %s' % (attr, info[attr])
94 |
95 | friends = facebook.friends.get()
96 | friends = facebook.users.getInfo(friends[0:5], ['name', 'birthday', 'relationship_status'])
97 |
98 | for friend in friends:
99 | if 'birthday' in friend:
100 | print friend['name'], 'has a birthday on', friend['birthday'], 'and is', friend['relationship_status']
101 | else:
102 | print friend['name'], 'has no birthday and is', friend['relationship_status']
103 |
104 | arefriends = facebook.friends.areFriends([friends[0]['uid']], [friends[1]['uid']])
105 |
106 | photos = facebook.photos.getAlbums(friends[1]['uid'])
107 | print photos
108 |
--------------------------------------------------------------------------------
/posediff:
--------------------------------------------------------------------------------
1 | diff --git a/facebook/__init__.py b/facebook/__init__.py
2 | index 3813142..e20fb6e 100644
3 | --- a/facebook/__init__.py
4 | +++ b/facebook/__init__.py
5 | @@ -136,6 +136,12 @@ METHODS = {
6 | ],
7 | },
8 |
9 | + # batch methods
10 | + 'batch': {
11 | + 'begin': [],
12 | + 'end': []
13 | + },
14 | +
15 | # feed methods
16 | 'feed': {
17 | 'publishStoryToUser': [
18 | @@ -812,6 +818,49 @@ class PhotosProxy(PhotosProxy):
19 | """Returns a guess at the MIME type of the file from the filename."""
20 | return str(mimetypes.guess_type(filename)[0]) or 'application/octet-stream'
21 |
22 | +class BatchProxy(object):
23 | + """
24 | + Provides Batch API calls:
25 | +
26 | + facebook.batch.begin
27 | + facebook.batch.end
28 | +
29 | + Example:
30 | +
31 | + >> b = self.fb.batch.begin()
32 | + >> b.users.getInfo([self.fb.uid], ['name', 'birthday', 'affiliations', 'sex'])
33 | + >> b.friends.get()
34 | + >> b.photos.getAlbums(self.fb.uid)
35 | + >> result = self.fb.batch.end()
36 | + result[0]()[0] #getInfo result
37 | +
38 | + result[1]() #friends.get result
39 | +
40 | + result[2]() #photos result
41 | +
42 | + """
43 | + def __init__(self, client, name):
44 | + self._client = client
45 | + self._name = name
46 | + self.myfb = None
47 | +
48 | + def begin(self):
49 | + #TODO: Ver si no es muy caro copiar todo el objeto, lo idea seria un objecto Session
50 | + import copy
51 | +
52 | + self.myfb = copy.deepcopy(self._client)
53 | + self.myfb.autocommit = False
54 | + return self.myfb
55 | +
56 | + def end(self):
57 | + """ Returns a list with the results as callables. """
58 | + if self.myfb is None:
59 | + raise RuntimeError('Called batch.end before batch.begin')
60 | + result = self.myfb.flush()
61 | + del self.myfb
62 | + return result
63 | +
64 | +
65 |
66 | class Facebook(object):
67 | """
68 | @@ -928,6 +977,11 @@ class Facebook(object):
69 | self.profile_update_time = None
70 | self.ext_perms = None
71 | self.proxy = proxy
72 | +
73 | + self.autocommit = True
74 | + self.active_transaction = []
75 | + self.secure_transaction = False
76 | +
77 | if facebook_url is None:
78 | self.facebook_url = FACEBOOK_URL
79 | else:
80 | @@ -1094,6 +1148,41 @@ class Facebook(object):
81 | # fix for bug of UnicodeEncodeError
82 | post_data = self.unicode_urlencode(self._build_post_args(method, args))
83 |
84 | + if not self.autocommit:
85 | + #TODO: Poner limite de 15 llamadas a la API
86 | + #TODO: Probar casos de escritura, no solo lectura
87 | + if not hasattr(self, 'active_transaction'):
88 | + self.active_transaction = []
89 | +
90 | + if secure:
91 | + self.secure_transaction = True
92 | +
93 | + self.active_transaction.append({'method': method, 'post_data': post_data})
94 | + return
95 | +
96 | + return self.send(method, post_data, secure)
97 | +
98 | + def flush(self):
99 | + post_data = self.unicode_urlencode( self._build_post_args('facebook.batch.run',
100 | + self._add_session_args({ 'method_feed':simplejson.dumps(
101 | + [query['post_data'] for query in self.active_transaction])})))
102 | +
103 | + print post_data
104 | +
105 | +
106 | + results = self.send('facebook.batch.run', post_data)
107 | + print results
108 | +
109 | +
110 | + from itertools import count, izip
111 | + from functools import partial
112 | +
113 | + results = [partial(self._parse_response,result, self.active_transaction[i]['method']) for result,i in izip(results, count())]
114 | +
115 | + self.active_transaction = []
116 | + return results
117 | +
118 | + def send(self,method=None, post_data=None, secure=False):
119 | if self.proxy:
120 | proxy_handler = urllib2.ProxyHandler(self.proxy)
121 | opener = urllib2.build_opener(proxy_handler)
122 |
--------------------------------------------------------------------------------
/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 | from paste.util.quoting import strip_html, html_quote, no_quote
27 | except ImportError:
28 | pass
29 | else:
30 | facebook = StackedObjectProxy(name="PyFacebook Facebook Connection")
31 |
32 |
33 | class CanvasRedirect(_HTTPMove):
34 |
35 | """This is for canvas redirects."""
36 |
37 | title = "See Other"
38 | code = 200
39 | template = ''
40 |
41 | def html(self, environ):
42 | """ text/html representation of the exception """
43 | body = self.make_body(environ, self.template, html_quote, no_quote)
44 | return body
45 |
46 | class FacebookWSGIMiddleware(object):
47 |
48 | """This is WSGI middleware for Facebook."""
49 |
50 | def __init__(self, app, config, facebook_class=Facebook):
51 | """Initialize the Facebook middleware.
52 |
53 | ``app``
54 | This is the WSGI application being wrapped.
55 |
56 | ``config``
57 | This is a dict containing the keys "pyfacebook.apikey" and
58 | "pyfacebook.secret".
59 |
60 | ``facebook_class``
61 | If you want to subclass the Facebook class, you can pass in
62 | your replacement here. Pylons users will want to use
63 | PylonsFacebook.
64 |
65 | """
66 | self.app = app
67 | self.config = config
68 | self.facebook_class = facebook_class
69 |
70 | def __call__(self, environ, start_response):
71 | config = self.config
72 | real_facebook = self.facebook_class(config["pyfacebook.apikey"],
73 | config["pyfacebook.secret"])
74 | registry = environ.get('paste.registry')
75 | if registry:
76 | registry.register(facebook, real_facebook)
77 | environ['pyfacebook.facebook'] = real_facebook
78 | return self.app(environ, start_response)
79 |
80 |
81 | # The remainder is Pylons specific.
82 |
83 | try:
84 | import pylons
85 | from pylons.controllers.util import redirect_to as pylons_redirect_to
86 | from routes import url_to
87 | except ImportError:
88 | pass
89 | else:
90 |
91 |
92 | class PylonsFacebook(Facebook):
93 |
94 | """Subclass Facebook to add Pylons goodies."""
95 |
96 | def check_session(self, request=None):
97 | """The request parameter is now optional."""
98 | if request is None:
99 | request = pylons.request
100 | return Facebook.check_session(self, request)
101 |
102 | # The Django request object is similar enough to the Paste
103 | # request object that check_session and validate_signature
104 | # should *just work*.
105 |
106 | def redirect_to(self, url):
107 | """Wrap Pylons' redirect_to function so that it works in_canvas.
108 |
109 | By the way, this won't work until after you call
110 | check_session().
111 |
112 | """
113 | if self.in_canvas:
114 | raise CanvasRedirect(url)
115 | pylons_redirect_to(url)
116 |
117 | def apps_url_for(self, *args, **kargs):
118 | """Like url_for, but starts with "http://apps.facebook.com"."""
119 | return "http://apps.facebook.com" + url_to(*args, **kargs)
120 |
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 |
--------------------------------------------------------------------------------
/bin/djangofb.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | if __name__ == '__main__':
4 | import sys, os, re
5 |
6 | def usage():
7 | sys.stderr.write('Usage: djangofb.py startapp \n')
8 | sys.exit(1)
9 |
10 | if len(sys.argv) not in (2, 3):
11 | usage()
12 |
13 | if sys.argv[1] != 'startapp':
14 | usage()
15 |
16 | app_name = len(sys.argv) == 3 and sys.argv[2] or 'fbapp'
17 |
18 | try:
19 | sys.path.insert(0, os.getcwd())
20 | import settings # Assumed to be in the same directory or current directory.
21 | except ImportError:
22 | sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r or in the current directory. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
23 | sys.exit(1)
24 |
25 | from django.core import management
26 |
27 | directory = management.setup_environ(settings)
28 |
29 | if hasattr(management, 'color'):
30 | # Current svn version of django
31 | from django.core.management.color import color_style
32 | style = color_style()
33 | else:
34 | # Compatibility with 0.96
35 | from django.core.management import style
36 |
37 | project_dir = os.path.normpath(os.path.join(directory, '..'))
38 | parent_dir = os.path.basename(project_dir)
39 | project_name = os.path.basename(directory)
40 | if app_name == project_name:
41 | sys.stderr.write(style.ERROR('Error: You cannot create an app with the same name (%r) as your project.\n' % app_name))
42 | sys.exit(1)
43 | if app_name == 'facebook':
44 | sys.stderr.write(style.ERROR('Error: You cannot name your app "facebook", since this can cause conflicts with imports in Python < 2.5.\n'))
45 | sys.exit(1)
46 | if not re.search(r'^\w+$', app_name):
47 | sys.stderr.write(style.ERROR('Error: %r is not a valid app name. Please use only numbers, letters and underscores.\n' % (app_name)))
48 | sys.exit(1)
49 |
50 | top_dir = os.path.join(directory, app_name)
51 | try:
52 | os.mkdir(top_dir)
53 | except OSError, e:
54 | sys.stderr.write(style.ERROR("Error: %s\n" % e))
55 | sys.exit(1)
56 |
57 | import facebook
58 |
59 | template_dir = os.path.join(facebook.__path__[0], 'djangofb', 'default_app')
60 |
61 | sys.stderr.write('Creating Facebook application %r...\n' % app_name)
62 |
63 | for d, subdirs, files in os.walk(template_dir):
64 | relative_dir = d[len(template_dir) + 1:]
65 | if relative_dir:
66 | os.mkdir(os.path.join(top_dir, relative_dir))
67 | subdirs[:] = [s for s in subdirs if not s.startswith('.')]
68 | for f in files:
69 | if f.endswith('.pyc'):
70 | continue
71 | path_old = os.path.join(d, f)
72 | path_new = os.path.join(top_dir, relative_dir, f)
73 | f_old = open(path_old, 'r')
74 | f_new = open(path_new, 'w')
75 | sys.stderr.write('Writing %s...\n' % path_new)
76 | f_new.write(f_old.read().replace('{{ project }}', project_name).replace('{{ app }}', app_name))
77 | f_new.close()
78 | f_old.close()
79 |
80 | sys.stderr.write('Done!\n\n')
81 |
82 | from django.conf import settings
83 |
84 | need_api_key = not hasattr(settings, 'FACEBOOK_API_KEY')
85 | need_middleware = not 'facebook.djangofb.FacebookMiddleware' in settings.MIDDLEWARE_CLASSES
86 | need_loader = not 'django.template.loaders.app_directories.load_template_source' in settings.TEMPLATE_LOADERS
87 | need_install_app = not '%s.%s' % (project_name, app_name) in settings.INSTALLED_APPS
88 |
89 | if need_api_key or need_middleware or need_loader or need_install_app:
90 | sys.stderr.write("""There are a couple of things you NEED to do before you can use this app:\n\n""")
91 | if need_api_key:
92 | sys.stderr.write(""" * Set FACEBOOK_API_KEY and FACEBOOK_SECRET_KEY to the appropriate values in settings.py\n\n""")
93 | if need_middleware:
94 | sys.stderr.write(""" * Add 'facebook.djangofb.FacebookMiddleware' to your MIDDLEWARE_CLASSES in settings.py\n\n""")
95 | if need_loader:
96 | sys.stderr.write(""" * Add 'django.template.loaders.app_directories.load_template_source' to your TEMPLATE_LOADERS in settings.py\n\n""")
97 | if need_install_app:
98 | sys.stderr.write(""" * Add '%s.%s' to your INSTALLED_APPS in settings.py\n\n""" % (project_name, app_name))
99 |
100 | sys.stderr.write("""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/'.
101 |
102 | Good luck!
103 |
104 | """ % (project_name, project_name, app_name, project_name))
105 |
--------------------------------------------------------------------------------
/facebook/djangofb/__init__.py:
--------------------------------------------------------------------------------
1 | import re
2 |
3 | import facebook
4 |
5 | from django.http import HttpResponse, HttpResponseRedirect
6 | from django.core.exceptions import ImproperlyConfigured
7 | from django.conf import settings
8 |
9 | try:
10 | from threading import local
11 | except ImportError:
12 | from django.utils._threading_local import local
13 |
14 | __all__ = ['Facebook', 'FacebookMiddleware', 'get_facebook_client', 'require_login', 'require_add']
15 |
16 | _thread_locals = local()
17 |
18 | class Facebook(facebook.Facebook):
19 | def redirect(self, url):
20 | """
21 | Helper for Django which redirects to another page. If inside a
22 | canvas page, writes a instead to achieve the same effect.
23 |
24 | """
25 | if self.in_canvas:
26 | return HttpResponse('' % (url, ))
27 | elif re.search("^https?:\/\/([^\/]*\.)?facebook\.com(:\d+)?", url.lower()):
28 | return HttpResponse('' % url)
29 | else:
30 | return HttpResponseRedirect(url)
31 |
32 |
33 | def get_facebook_client():
34 | """
35 | Get the current Facebook object for the calling thread.
36 |
37 | """
38 | try:
39 | return _thread_locals.facebook
40 | except AttributeError:
41 | raise ImproperlyConfigured('Make sure you have the Facebook middleware installed.')
42 |
43 |
44 | def require_login(next=None, internal=None):
45 | """
46 | Decorator for Django views that requires the user to be logged in.
47 | The FacebookMiddleware must be installed.
48 |
49 | Standard usage:
50 | @require_login()
51 | def some_view(request):
52 | ...
53 |
54 | Redirecting after login:
55 | To use the 'next' parameter to redirect to a specific page after login, a callable should
56 | return a path relative to the Post-add URL. 'next' can also be an integer specifying how many
57 | parts of request.path to strip to find the relative URL of the canvas page. If 'next' is None,
58 | settings.callback_path and settings.app_name are checked to redirect to the same page after logging
59 | in. (This is the default behavior.)
60 | @require_login(next=some_callable)
61 | def some_view(request):
62 | ...
63 | """
64 | def decorator(view):
65 | def newview(request, *args, **kwargs):
66 | next = newview.next
67 | internal = newview.internal
68 |
69 | try:
70 | fb = request.facebook
71 | except:
72 | raise ImproperlyConfigured('Make sure you have the Facebook middleware installed.')
73 |
74 | if internal is None:
75 | internal = request.facebook.internal
76 |
77 | if callable(next):
78 | next = next(request.path)
79 | elif isinstance(next, int):
80 | next = '/'.join(request.path.split('/')[next + 1:])
81 | elif next is None and fb.callback_path and request.path.startswith(fb.callback_path):
82 | next = request.path[len(fb.callback_path):]
83 | elif not isinstance(next, str):
84 | next = ''
85 |
86 | if not fb.check_session(request):
87 | #If user has never logged in before, the get_login_url will redirect to the TOS page
88 | return fb.redirect(fb.get_login_url(next=next))
89 |
90 | if internal and request.method == 'GET' and fb.app_name:
91 | return fb.redirect('%s%s' % (fb.get_app_url(), next))
92 |
93 | return view(request, *args, **kwargs)
94 | newview.next = next
95 | newview.internal = internal
96 | return newview
97 | return decorator
98 |
99 |
100 | def require_add(next=None, internal=None, on_install=None):
101 | """
102 | Decorator for Django views that requires application installation.
103 | The FacebookMiddleware must be installed.
104 |
105 | Standard usage:
106 | @require_add()
107 | def some_view(request):
108 | ...
109 |
110 | Redirecting after installation:
111 | To use the 'next' parameter to redirect to a specific page after login, a callable should
112 | return a path relative to the Post-add URL. 'next' can also be an integer specifying how many
113 | parts of request.path to strip to find the relative URL of the canvas page. If 'next' is None,
114 | settings.callback_path and settings.app_name are checked to redirect to the same page after logging
115 | in. (This is the default behavior.)
116 | @require_add(next=some_callable)
117 | def some_view(request):
118 | ...
119 |
120 | Post-install processing:
121 | Set the on_install parameter to a callable in order to handle special post-install processing.
122 | The callable should take a request object as the parameter.
123 | @require_add(on_install=some_callable)
124 | def some_view(request):
125 | ...
126 | """
127 | def decorator(view):
128 | def newview(request, *args, **kwargs):
129 | next = newview.next
130 | internal = newview.internal
131 |
132 | try:
133 | fb = request.facebook
134 | except:
135 | raise ImproperlyConfigured('Make sure you have the Facebook middleware installed.')
136 |
137 | if internal is None:
138 | internal = request.facebook.internal
139 |
140 | if callable(next):
141 | next = next(request.path)
142 | elif isinstance(next, int):
143 | next = '/'.join(request.path.split('/')[next + 1:])
144 | elif next is None and fb.callback_path and request.path.startswith(fb.callback_path):
145 | next = request.path[len(fb.callback_path):]
146 | else:
147 | next = ''
148 |
149 | if not fb.check_session(request):
150 | if fb.added:
151 | if request.method == 'GET' and fb.app_name:
152 | return fb.redirect('%s%s' % (fb.get_app_url(), next))
153 | return fb.redirect(fb.get_login_url(next=next))
154 | else:
155 | return fb.redirect(fb.get_add_url(next=next))
156 |
157 | if not fb.added:
158 | return fb.redirect(fb.get_add_url(next=next))
159 |
160 | if 'installed' in request.GET and callable(on_install):
161 | on_install(request)
162 |
163 | if internal and request.method == 'GET' and fb.app_name:
164 | return fb.redirect('%s%s' % (fb.get_app_url(), next))
165 |
166 | return view(request, *args, **kwargs)
167 | newview.next = next
168 | newview.internal = internal
169 | return newview
170 | return decorator
171 |
172 | # try to preserve the argspecs
173 | try:
174 | import decorator
175 | except ImportError:
176 | pass
177 | else:
178 | def updater(f):
179 | def updated(*args, **kwargs):
180 | original = f(*args, **kwargs)
181 | def newdecorator(view):
182 | return decorator.new_wrapper(original(view), view)
183 | return decorator.new_wrapper(newdecorator, original)
184 | return decorator.new_wrapper(updated, f)
185 | require_login = updater(require_login)
186 | require_add = updater(require_add)
187 |
188 | class FacebookMiddleware(object):
189 | """
190 | Middleware that attaches a Facebook object to every incoming request.
191 | The Facebook object created can also be accessed from models for the
192 | current thread by using get_facebook_client().
193 |
194 | """
195 |
196 | def __init__(self, api_key=None, secret_key=None, app_name=None, callback_path=None, internal=None):
197 | self.api_key = api_key or settings.FACEBOOK_API_KEY
198 | self.secret_key = secret_key or settings.FACEBOOK_SECRET_KEY
199 | self.app_name = app_name or getattr(settings, 'FACEBOOK_APP_NAME', None)
200 | self.callback_path = callback_path or getattr(settings, 'FACEBOOK_CALLBACK_PATH', None)
201 | self.internal = internal or getattr(settings, 'FACEBOOK_INTERNAL', True)
202 | self.proxy = None
203 | if getattr(settings, 'USE_HTTP_PROXY', False):
204 | self.proxy = settings.HTTP_PROXY
205 |
206 | def process_request(self, request):
207 | _thread_locals.facebook = request.facebook = Facebook(self.api_key, self.secret_key, app_name=self.app_name, callback_path=self.callback_path, internal=self.internal, proxy=self.proxy)
208 | if not self.internal and 'facebook_session_key' in request.session and 'facebook_user_id' in request.session:
209 | request.facebook.session_key = request.session['facebook_session_key']
210 | request.facebook.uid = request.session['facebook_user_id']
211 |
212 | def process_response(self, request, response):
213 | if not self.internal and request.facebook.session_key and request.facebook.uid:
214 | request.session['facebook_session_key'] = request.facebook.session_key
215 | request.session['facebook_user_id'] = request.facebook.uid
216 | return response
217 |
--------------------------------------------------------------------------------
/tests/minimock.py:
--------------------------------------------------------------------------------
1 | # (c) 2006 Ian Bicking, Mike Beachy, and contributors
2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
3 | r"""
4 | minimock is a simple library for doing Mock objects with doctest.
5 | When using doctest, mock objects can be very simple.
6 |
7 | Here's an example of something we might test, a simple email sender::
8 |
9 | >>> import smtplib
10 | >>> def send_email(from_addr, to_addr, subject, body):
11 | ... conn = smtplib.SMTP('localhost')
12 | ... msg = 'To: %s\nFrom: %s\nSubject: %s\n\n%s' % (
13 | ... to_addr, from_addr, subject, body)
14 | ... conn.sendmail(from_addr, [to_addr], msg)
15 | ... conn.quit()
16 |
17 | Now we want to make a mock ``smtplib.SMTP`` object. We'll have to
18 | inject our mock into the ``smtplib`` module::
19 |
20 | >>> smtplib.SMTP = Mock('smtplib.SMTP')
21 | >>> smtplib.SMTP.mock_returns = Mock('smtp_connection')
22 |
23 | Now we do the test::
24 |
25 | >>> send_email('ianb@colorstudy.com', 'joe@example.com',
26 | ... 'Hi there!', 'How is it going?')
27 | Called smtplib.SMTP('localhost')
28 | Called smtp_connection.sendmail(
29 | 'ianb@colorstudy.com',
30 | ['joe@example.com'],
31 | 'To: joe@example.com\nFrom: ianb@colorstudy.com\nSubject: Hi there!\n\nHow is it going?')
32 | Called smtp_connection.quit()
33 |
34 | Voila! We've tested implicitly that no unexpected methods were called
35 | on the object. We've also tested the arguments that the mock object
36 | got. We've provided fake return calls (for the ``smtplib.SMTP()``
37 | constructor). These are all the core parts of a mock library. The
38 | implementation is simple because most of the work is done by doctest.
39 | """
40 |
41 | import warnings
42 | warnings.warn(
43 | "The module from http://svn.colorstudy.com/home/ianb/recipes/minimock.py is deprecated; "
44 | "please install the MiniMock package",
45 | DeprecationWarning, stacklevel=2)
46 |
47 | __all__ = ["mock", "restore", "Mock"]
48 |
49 | import inspect
50 |
51 | # A list of mocked objects. Each item is a tuple of (original object,
52 | # namespace dict, object name, and a list of object attributes).
53 | #
54 | mocked = []
55 |
56 | def lookup_by_name(name, nsdicts):
57 | """
58 | Look up an object by name from a sequence of namespace dictionaries.
59 | Returns a tuple of (nsdict, object, attributes); nsdict is the
60 | dictionary the name was found in, object is the base object the name is
61 | bound to, and the attributes list is the chain of attributes of the
62 | object that complete the name.
63 |
64 | >>> import os
65 | >>> nsdict, name, attributes = lookup_by_name("os.path.isdir",
66 | ... (locals(),))
67 | >>> name, attributes
68 | ('os', ['path', 'isdir'])
69 | >>> nsdict, name, attributes = lookup_by_name("os.monkey", (locals(),))
70 | Traceback (most recent call last):
71 | ...
72 | NameError: name 'os.monkey' is not defined
73 |
74 | """
75 | for nsdict in nsdicts:
76 | attrs = name.split(".")
77 | names = []
78 |
79 | while attrs:
80 | names.append(attrs.pop(0))
81 | obj_name = ".".join(names)
82 |
83 | if obj_name in nsdict:
84 | attr_copy = attrs[:]
85 | tmp = nsdict[obj_name]
86 | try:
87 | while attr_copy:
88 | tmp = getattr(tmp, attr_copy.pop(0))
89 | except AttributeError:
90 | pass
91 | else:
92 | return nsdict, obj_name, attrs
93 |
94 | raise NameError("name '%s' is not defined" % name)
95 |
96 | def mock(name, nsdicts=None, mock_obj=None, **kw):
97 | """
98 | Mock the named object, placing a Mock instance in the correct namespace
99 | dictionary. If no iterable of namespace dicts is provided, use
100 | introspection to get the locals and globals of the caller of this
101 | function.
102 |
103 | All additional keyword args are passed on to the Mock object
104 | initializer.
105 |
106 | An example of how os.path.isfile is replaced:
107 |
108 | >>> import os
109 | >>> os.path.isfile
110 |
111 | >>> isfile_id = id(os.path.isfile)
112 | >>> mock("os.path.isfile", returns=True)
113 | >>> os.path.isfile
114 |
115 | >>> os.path.isfile("/foo/bar/baz")
116 | Called os.path.isfile('/foo/bar/baz')
117 | True
118 | >>> mock_id = id(os.path.isfile)
119 | >>> mock_id != isfile_id
120 | True
121 |
122 | A second mock object will replace the first, but the original object
123 | will be the one replaced with the replace() function.
124 |
125 | >>> mock("os.path.isfile", returns=False)
126 | >>> mock_id != id(os.path.isfile)
127 | True
128 | >>> restore()
129 | >>> os.path.isfile
130 |
131 | >>> isfile_id == id(os.path.isfile)
132 | True
133 |
134 | """
135 | if nsdicts is None:
136 | stack = inspect.stack()
137 | try:
138 | # stack[1][0] is the frame object of the caller to this function
139 | globals_ = stack[1][0].f_globals
140 | locals_ = stack[1][0].f_locals
141 | nsdicts = (locals_, globals_)
142 | finally:
143 | del(stack)
144 |
145 | if mock_obj is None:
146 | mock_obj = Mock(name, **kw)
147 |
148 | nsdict, obj_name, attrs = lookup_by_name(name, nsdicts)
149 |
150 | # Get the original object and replace it with the mock object.
151 | tmp = nsdict[obj_name]
152 | if not attrs:
153 | original = tmp
154 | nsdict[obj_name] = mock_obj
155 | else:
156 | for attr in attrs[:-1]:
157 | tmp = getattr(tmp, attr)
158 | original = getattr(tmp, attrs[-1])
159 | setattr(tmp, attrs[-1], mock_obj)
160 |
161 | mocked.append((original, nsdict, obj_name, attrs))
162 |
163 | def restore():
164 | """
165 | Restore all mocked objects.
166 |
167 | """
168 | global mocked
169 |
170 | # Restore the objects in the reverse order of their mocking to assure
171 | # the original state is retrieved.
172 | while mocked:
173 | original, nsdict, name, attrs = mocked.pop()
174 | if not attrs:
175 | nsdict[name] = original
176 | else:
177 | tmp = nsdict[name]
178 | for attr in attrs[:-1]:
179 | tmp = getattr(tmp, attr)
180 | setattr(tmp, attrs[-1], original)
181 | return
182 |
183 | class Mock(object):
184 |
185 | def __init__(self, name, returns=None, returns_iter=None,
186 | returns_func=None, raises=None):
187 | self.mock_name = name
188 | self.mock_returns = returns
189 | if returns_iter is not None:
190 | returns_iter = iter(returns_iter)
191 | self.mock_returns_iter = returns_iter
192 | self.mock_returns_func = returns_func
193 | self.mock_raises = raises
194 | self.mock_attrs = {}
195 |
196 | def __repr__(self):
197 | return '' % (hex(id(self)), self.mock_name)
198 |
199 | def __call__(self, *args, **kw):
200 | parts = [repr(a) for a in args]
201 | parts.extend(
202 | '%s=%r' % (items) for items in sorted(kw.items()))
203 | msg = 'Called %s(%s)' % (self.mock_name, ', '.join(parts))
204 | if len(msg) > 80:
205 | msg = 'Called %s(\n %s)' % (
206 | self.mock_name, ',\n '.join(parts))
207 | print msg
208 | return self._mock_return(*args, **kw)
209 |
210 | def _mock_return(self, *args, **kw):
211 | if self.mock_raises is not None:
212 | raise self.mock_raises
213 | elif self.mock_returns is not None:
214 | return self.mock_returns
215 | elif self.mock_returns_iter is not None:
216 | try:
217 | return self.mock_returns_iter.next()
218 | except StopIteration:
219 | raise Exception("No more mock return values are present.")
220 | elif self.mock_returns_func is not None:
221 | return self.mock_returns_func(*args, **kw)
222 | else:
223 | return None
224 |
225 | def __getattr__(self, attr):
226 | if attr not in self.mock_attrs:
227 | if self.mock_name:
228 | new_name = self.mock_name + '.' + attr
229 | else:
230 | new_name = attr
231 | self.mock_attrs[attr] = Mock(new_name)
232 | return self.mock_attrs[attr]
233 |
234 | __test__ = {
235 | "mock" :
236 | r"""
237 | An additional test for mocking a function accessed directly (i.e.
238 | not via object attributes).
239 |
240 | >>> import os
241 | >>> rename = os.rename
242 | >>> orig_id = id(rename)
243 | >>> mock("rename")
244 | >>> mock_id = id(rename)
245 | >>> mock("rename")
246 | >>> mock_id != id(rename)
247 | True
248 | >>> restore()
249 | >>> orig_id == id(rename) == id(os.rename)
250 | True
251 |
252 | The example from the module docstring, done with the mock/restore
253 | functions.
254 |
255 | >>> import smtplib
256 | >>> def send_email(from_addr, to_addr, subject, body):
257 | ... conn = smtplib.SMTP('localhost')
258 | ... msg = 'To: %s\nFrom: %s\nSubject: %s\n\n%s' % (
259 | ... to_addr, from_addr, subject, body)
260 | ... conn.sendmail(from_addr, [to_addr], msg)
261 | ... conn.quit()
262 |
263 | >>> mock("smtplib.SMTP", returns=Mock('smtp_connection'))
264 | >>> send_email('ianb@colorstudy.com', 'joe@example.com',
265 | ... 'Hi there!', 'How is it going?')
266 | Called smtplib.SMTP('localhost')
267 | Called smtp_connection.sendmail(
268 | 'ianb@colorstudy.com',
269 | ['joe@example.com'],
270 | 'To: joe@example.com\nFrom: ianb@colorstudy.com\nSubject: Hi there!\n\nHow is it going?')
271 | Called smtp_connection.quit()
272 | >>> restore()
273 |
274 | """,
275 | }
276 |
277 | if __name__ == '__main__':
278 | import doctest
279 | doctest.testmod(optionflags=doctest.ELLIPSIS)
280 |
--------------------------------------------------------------------------------
/tests/test.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import sys
3 | import os
4 | import facebook
5 | import urllib2
6 | import md5
7 | try:
8 | import simplejson
9 | except ImportError:
10 | from django.utils import simplejson
11 | import httplib
12 | from minimock import Mock
13 |
14 | my_api_key = "e1e9cfeb5e0d7a52e4fbd5d09e1b873e"
15 | my_secret_key = "1bebae7283f5b79aaf9b851addd55b90"
16 | #'{"error_code":100,\
17 | #"error_msg":"Invalid parameter",\
18 | #"request_args":[{"key":"format","value":"JSON"},\
19 | #{"key":"auth_token","value":"24626e24bb12919f2f142145070542e8"},\
20 | #{"key":"sig","value":"36af2af3b93da784149301e77cb1621a"},\
21 | #{"key":"v","value":"1.0"},\
22 | #{"key":"api_key","value":"e1e9cfeb5e0d7a52e4fbd5d09e1b873e"},\
23 | #{"key":"method","value":"facebook.auth.getSession"}]}'
24 | response_str = '{"stuff":"abcd"}'
25 | class MyUrlOpen:
26 | def __init__(self,*args,**kwargs):
27 | pass
28 |
29 | def read(self):
30 | global response_str
31 | return response_str
32 |
33 | class pyfacebook_UnitTests(unittest.TestCase):
34 | def setUp(self):
35 | facebook.urllib2.urlopen = Mock('urllib2.urlopen')
36 | facebook.urllib2.urlopen.mock_returns_func = MyUrlOpen
37 | pass
38 |
39 | def tearDown(self):
40 | pass
41 |
42 | def login(self):
43 | pass
44 |
45 | def test1(self):
46 | f = facebook.Facebook(api_key=my_api_key, secret_key=my_secret_key)
47 | f.login = self.login
48 | self.assertEquals(f.api_key,my_api_key)
49 | self.assertEquals(f.secret_key,my_secret_key)
50 | self.assertEquals(f.auth_token,None)
51 | self.assertEquals(f.app_name,None)
52 | self.assertEquals(f.callback_path,None)
53 | self.assertEquals(f.internal,None)
54 |
55 | def test2(self):
56 | args = {"arg1":"a","arg2":"b","arg3":"c"}
57 | hasher = md5.new(''.join(['%s=%s' % (x, args[x]) for x in sorted(args.keys())]))
58 | hasher.update("acdnj")
59 | f = facebook.Facebook(api_key="abcdf", secret_key="acdnj")
60 | f.login = self.login
61 | digest = f._hash_args(args)
62 | self.assertEquals(hasher.hexdigest(),digest)
63 | hasher = md5.new(''.join(['%s=%s' % (x, args[x]) for x in sorted(args.keys())]))
64 | hasher.update("klmn")
65 | # trunk code has error hash.updated instead of hash.update
66 | digest = f._hash_args(args,secret="klmn")
67 | self.assertEquals(hasher.hexdigest(),digest)
68 |
69 | hasher = md5.new(''.join(['%s=%s' % (x, args[x]) for x in sorted(args.keys())]))
70 | f.secret = "klmn"
71 | hasher.update(f.secret)
72 | # trunk code has error hash.updated instead of hash.update
73 | digest = f._hash_args(args)
74 | self.assertEquals(hasher.hexdigest(),digest)
75 |
76 | def test3(self):
77 | global response_str
78 | response = {'stuff':'abcd'}
79 | response_str = simplejson.dumps(response)
80 | fb = facebook.Facebook(my_api_key, my_secret_key)
81 | fb.login = self.login
82 | fb.auth.createToken()
83 | self.assertEquals(str(fb.auth_token['stuff']),"abcd")
84 | fb.login()
85 | response = {"session_key":"key","uid":"my_uid","secret":"my_secret","expires":"my_expires"}
86 | response_str = simplejson.dumps(response)
87 | res = fb.auth.getSession()
88 | self.assertEquals(str(res["expires"]),response["expires"])
89 | self.assertEquals(str(res["secret"]),response["secret"])
90 | self.assertEquals(str(res["session_key"]),response["session_key"])
91 | self.assertEquals(str(res["uid"]),response["uid"])
92 |
93 | def test4(self):
94 | global response_str
95 | response = 'abcdef'
96 | response_str = simplejson.dumps(response)
97 | fb = facebook.Facebook(my_api_key, my_secret_key)
98 | fb.login = self.login
99 | fb.auth.createToken()
100 | self.assertEquals(str(fb.auth_token),"abcdef")
101 | url = fb.get_login_url(next="nowhere", popup=True, canvas=True)
102 | self.assertEquals(url,
103 | 'http://www.facebook.com/login.php?canvas=1&popup=1&auth_token=abcdef&next=nowhere&v=1.0&api_key=%s'%(my_api_key,))
104 |
105 | def test5(self):
106 | class Request:
107 | def __init__(self,post,get,method):
108 | self.POST = post
109 | self.GET = get
110 | self.method = method
111 |
112 | req = Request({'fb_sig_in_canvas':1},{},'POST')
113 | fb = facebook.Facebook(my_api_key, my_secret_key)
114 | fb.login = self.login
115 | res = fb.check_session(req)
116 | self.assertFalse(res)
117 | req = Request({'fb_sig':1},{},'POST')
118 | res = fb.check_session(req)
119 | self.assertFalse(res)
120 | req = Request({'fb_sig':fb._hash_args({'in_canvas':'1',
121 | 'added':'1',
122 | 'expires':'1',
123 | 'friends':'joe,mary',
124 | 'session_key':'abc',
125 | 'user':'bob'}),
126 | 'fb_sig_in_canvas':'1',
127 | 'fb_sig_added':'1',
128 | 'fb_sig_expires':'1',
129 | 'fb_sig_friends':'joe,mary',
130 | 'fb_sig_session_key':'abc',
131 | 'fb_sig_user':'bob'},
132 | {},'POST')
133 | res = fb.check_session(req)
134 | self.assertTrue(res)
135 | fb = facebook.Facebook(my_api_key, my_secret_key)
136 | fb.login = self.login
137 | req = Request({'fb_sig':fb._hash_args({'in_canvas':'1',
138 | 'added':'1',
139 | 'expires':'1',
140 | 'friends':'',
141 | 'session_key':'abc',
142 | 'user':'bob'}),
143 | 'fb_sig_in_canvas':'1',
144 | 'fb_sig_added':'1',
145 | 'fb_sig_expires':'1',
146 | 'fb_sig_friends':'',
147 | 'fb_sig_session_key':'abc',
148 | 'fb_sig_user':'bob'},
149 | {},'POST')
150 | res = fb.check_session(req)
151 | self.assertTrue(res)
152 | fb = facebook.Facebook(my_api_key, my_secret_key)
153 | fb.login = self.login
154 | req = Request({'fb_sig':fb._hash_args({'in_canvas':'1',
155 | 'added':'1',
156 | 'expires':'1',
157 | 'friends':'',
158 | 'session_key':'abc',
159 | 'page_id':'id'}),
160 | 'fb_sig_in_canvas':'1',
161 | 'fb_sig_added':'1',
162 | 'fb_sig_expires':'1',
163 | 'fb_sig_friends':'',
164 | 'fb_sig_session_key':'abc',
165 | 'fb_sig_page_id':'id'},
166 | {},'POST')
167 | res = fb.check_session(req)
168 | self.assertTrue(res)
169 |
170 | def test6(self):
171 | global response_str
172 | response = 'abcdef'
173 | response_str = simplejson.dumps(response)
174 | fb = facebook.Facebook(my_api_key, my_secret_key)
175 | fb.login = self.login
176 | fb.auth.createToken()
177 | # self.failUnlessRaises(RuntimeError,fb._add_session_args)
178 | response = {"session_key":"key","uid":"my_uid","secret":"my_secret","expires":"my_expires"}
179 | response_str = simplejson.dumps(response)
180 | fb.auth.getSession()
181 | args = fb._add_session_args()
182 |
183 | def test7(self):
184 | global response_str
185 | response = 'abcdef'
186 | response_str = simplejson.dumps(response)
187 | fb = facebook.Facebook(my_api_key, my_secret_key)
188 | fb.login = self.login
189 | fb.auth.createToken()
190 | self.assertEquals(str(fb.auth_token),"abcdef")
191 | url = fb.get_authorize_url(next="next",next_cancel="next_cancel")
192 | self.assertEquals(url,
193 | 'http://www.facebook.com/authorize.php?api_key=%s&next_cancel=next_cancel&v=1.0&next=next' % (my_api_key,))
194 |
195 | def test8(self):
196 | class Request:
197 | def __init__(self,post,get,method):
198 | self.POST = post
199 | self.GET = get
200 | self.method = method
201 |
202 | global response_str
203 | response = {"session_key":"abcdef","uid":"my_uid","secret":"my_secret","expires":"my_expires"}
204 | response_str = simplejson.dumps(response)
205 | req = Request({},{'installed':1,'fb_page_id':'id','auth_token':'abcdef'},'GET')
206 | fb = facebook.Facebook(my_api_key, my_secret_key)
207 | fb.login = self.login
208 | res = fb.check_session(req)
209 | self.assertTrue(res)
210 |
211 | def test9(self):
212 | global response_str
213 | response = 'abcdef'
214 | response_str = simplejson.dumps(response)
215 | fb = facebook.Facebook(my_api_key, my_secret_key)
216 | fb.login = self.login
217 | fb.auth.createToken()
218 | self.assertEquals(str(fb.auth_token),"abcdef")
219 | url = fb.get_add_url(next="next")
220 | self.assertEquals(url,
221 | 'http://www.facebook.com/install.php?api_key=%s&v=1.0&next=next' % (my_api_key,))
222 |
223 | def send(self,xml):
224 | self.xml = xml
225 |
226 | def test10(self):
227 | import Image
228 | image1 = Image.new("RGB", (400, 300), (255, 255, 255))
229 | filename = "image_file.jpg"
230 | image1.save(filename)
231 | global response_str
232 | fb = facebook.Facebook(my_api_key, my_secret_key)
233 | fb.login = self.login
234 |
235 | facebook.httplib.HTTP = Mock('httplib.HTTP')
236 | http_connection = Mock('http_connection')
237 | facebook.httplib.HTTP.mock_returns = http_connection
238 | http_connection.send.mock_returns_func = self.send
239 | def _http_passes():
240 | return [200,]
241 | http_connection.getreply.mock_returns_func = _http_passes
242 |
243 | def read():
244 | response = {"stuff":"stuff"}
245 | response_str = simplejson.dumps(response)
246 | return response_str
247 | http_connection.file.read.mock_returns_func = read
248 |
249 | response = {"session_key":"key","uid":"my_uid","secret":"my_secret","expires":"my_expires"}
250 | response_str = simplejson.dumps(response)
251 | res = fb.auth.getSession()
252 | result = fb.photos.upload(image=filename,aid="aid",caption="a caption")
253 | self.assertEquals(str(result["stuff"]),"stuff")
254 | os.remove(filename)
255 |
256 | if __name__ == "__main__":
257 |
258 | # Build the test suite
259 | suite = unittest.TestSuite()
260 | suite.addTest(unittest.makeSuite(pyfacebook_UnitTests))
261 |
262 | # Execute the test suite
263 | print("Testing Proxy class\n")
264 | result = unittest.TextTestRunner(verbosity=2).run(suite)
265 | sys.exit(len(result.errors) + len(result.failures))
266 |
267 |
--------------------------------------------------------------------------------
/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 md5
48 | import sys
49 | import time
50 | import struct
51 | import urllib
52 | import urllib2
53 | import httplib
54 | import hashlib
55 | import binascii
56 | import urlparse
57 | import mimetypes
58 |
59 | # try to use simplejson first, otherwise fallback to XML
60 | RESPONSE_FORMAT = 'JSON'
61 | try:
62 | import json as simplejson
63 | except ImportError:
64 | try:
65 | import simplejson
66 | except ImportError:
67 | try:
68 | from django.utils import simplejson
69 | except ImportError:
70 | try:
71 | import jsonlib as simplejson
72 | simplejson.loads
73 | except (ImportError, AttributeError):
74 | from xml.dom import minidom
75 | RESPONSE_FORMAT = 'XML'
76 |
77 | # support Google App Engine. GAE does not have a working urllib.urlopen.
78 | try:
79 | from google.appengine.api import urlfetch
80 |
81 | def urlread(url, data=None, headers=None):
82 | if data is not None:
83 | if headers is None:
84 | headers = {"Content-type": "application/x-www-form-urlencoded"}
85 | method = urlfetch.POST
86 | else:
87 | if headers is None:
88 | headers = {}
89 | method = urlfetch.GET
90 |
91 | result = urlfetch.fetch(url, method=method,
92 | payload=data, headers=headers)
93 |
94 | if result.status_code == 200:
95 | return result.content
96 | else:
97 | raise urllib2.URLError("fetch error url=%s, code=%d" % (url, result.status_code))
98 |
99 | except ImportError:
100 | def urlread(url, data=None):
101 | res = urllib2.urlopen(url, data=data)
102 | return res.read()
103 |
104 | __all__ = ['Facebook']
105 |
106 | VERSION = '0.1'
107 |
108 | FACEBOOK_URL = 'http://api.facebook.com/restserver.php'
109 | FACEBOOK_SECURE_URL = 'https://api.facebook.com/restserver.php'
110 |
111 | class json(object): pass
112 |
113 | # simple IDL for the Facebook API
114 | METHODS = {
115 | 'application': {
116 | 'getPublicInfo': [
117 | ('application_id', int, ['optional']),
118 | ('application_api_key', str, ['optional']),
119 | ('application_canvas_name ', str,['optional']),
120 | ],
121 | },
122 |
123 | # admin methods
124 | 'admin': {
125 | 'getAllocation': [
126 | ('integration_point_name', str, []),
127 | ],
128 | },
129 |
130 | # feed methods
131 | 'feed': {
132 | 'publishStoryToUser': [
133 | ('title', str, []),
134 | ('body', str, ['optional']),
135 | ('image_1', str, ['optional']),
136 | ('image_1_link', str, ['optional']),
137 | ('image_2', str, ['optional']),
138 | ('image_2_link', str, ['optional']),
139 | ('image_3', str, ['optional']),
140 | ('image_3_link', str, ['optional']),
141 | ('image_4', str, ['optional']),
142 | ('image_4_link', str, ['optional']),
143 | ('priority', int, ['optional']),
144 | ],
145 |
146 | 'publishActionOfUser': [
147 | ('title', str, []),
148 | ('body', str, ['optional']),
149 | ('image_1', str, ['optional']),
150 | ('image_1_link', str, ['optional']),
151 | ('image_2', str, ['optional']),
152 | ('image_2_link', str, ['optional']),
153 | ('image_3', str, ['optional']),
154 | ('image_3_link', str, ['optional']),
155 | ('image_4', str, ['optional']),
156 | ('image_4_link', str, ['optional']),
157 | ('priority', int, ['optional']),
158 | ],
159 |
160 | 'publishTemplatizedAction': [
161 | ('title_template', str, []),
162 | ('page_actor_id', int, ['optional']),
163 | ('title_data', json, ['optional']),
164 | ('body_template', str, ['optional']),
165 | ('body_data', json, ['optional']),
166 | ('body_general', str, ['optional']),
167 | ('image_1', str, ['optional']),
168 | ('image_1_link', str, ['optional']),
169 | ('image_2', str, ['optional']),
170 | ('image_2_link', str, ['optional']),
171 | ('image_3', str, ['optional']),
172 | ('image_3_link', str, ['optional']),
173 | ('image_4', str, ['optional']),
174 | ('image_4_link', str, ['optional']),
175 | ('target_ids', list, ['optional']),
176 | ],
177 |
178 | 'registerTemplateBundle': [
179 | ('one_line_story_templates', json, []),
180 | ('short_story_templates', json, ['optional']),
181 | ('full_story_template', json, ['optional']),
182 | ('action_links', json, ['optional']),
183 | ],
184 |
185 | 'deactivateTemplateBundleByID': [
186 | ('template_bundle_id', int, []),
187 | ],
188 |
189 | 'getRegisteredTemplateBundles': [],
190 |
191 | 'getRegisteredTemplateBundleByID': [
192 | ('template_bundle_id', str, []),
193 | ],
194 |
195 | 'publishUserAction': [
196 | ('template_bundle_id', int, []),
197 | ('template_data', json, ['optional']),
198 | ('target_ids', list, ['optional']),
199 | ('body_general', str, ['optional']),
200 | ],
201 | },
202 |
203 | # fql methods
204 | 'fql': {
205 | 'query': [
206 | ('query', str, []),
207 | ],
208 | },
209 |
210 | # friends methods
211 | 'friends': {
212 | 'areFriends': [
213 | ('uids1', list, []),
214 | ('uids2', list, []),
215 | ],
216 |
217 | 'get': [
218 | ('flid', int, ['optional']),
219 | ],
220 |
221 | 'getLists': [],
222 |
223 | 'getAppUsers': [],
224 | },
225 |
226 | # notifications methods
227 | 'notifications': {
228 | 'get': [],
229 |
230 | 'send': [
231 | ('to_ids', list, []),
232 | ('notification', str, []),
233 | ('email', str, ['optional']),
234 | ('type', str, ['optional']),
235 | ],
236 |
237 | 'sendRequest': [
238 | ('to_ids', list, []),
239 | ('type', str, []),
240 | ('content', str, []),
241 | ('image', str, []),
242 | ('invite', bool, []),
243 | ],
244 |
245 | 'sendEmail': [
246 | ('recipients', list, []),
247 | ('subject', str, []),
248 | ('text', str, ['optional']),
249 | ('fbml', str, ['optional']),
250 | ]
251 | },
252 |
253 | # profile methods
254 | 'profile': {
255 | 'setFBML': [
256 | ('markup', str, ['optional']),
257 | ('uid', int, ['optional']),
258 | ('profile', str, ['optional']),
259 | ('profile_action', str, ['optional']),
260 | ('mobile_fbml', str, ['optional']),
261 | ('profile_main', str, ['optional']),
262 | ],
263 |
264 | 'getFBML': [
265 | ('uid', int, ['optional']),
266 | ('type', int, ['optional']),
267 | ],
268 |
269 | 'setInfo': [
270 | ('title', str, []),
271 | ('type', int, []),
272 | ('info_fields', json, []),
273 | ('uid', int, []),
274 | ],
275 |
276 | 'getInfo': [
277 | ('uid', int, []),
278 | ],
279 |
280 | 'setInfoOptions': [
281 | ('field', str, []),
282 | ('options', json, []),
283 | ],
284 |
285 | 'getInfoOptions': [
286 | ('field', str, []),
287 | ],
288 | },
289 |
290 | # users methods
291 | 'users': {
292 | 'getInfo': [
293 | ('uids', list, []),
294 | ('fields', list, [('default', ['name'])]),
295 | ],
296 |
297 | 'getStandardInfo': [
298 | ('uids', list, []),
299 | ('fields', list, [('default', ['uid'])]),
300 | ],
301 |
302 | 'getLoggedInUser': [],
303 |
304 | 'isAppAdded': [],
305 |
306 | 'hasAppPermission': [
307 | ('ext_perm', str, []),
308 | ('uid', int, ['optional']),
309 | ],
310 |
311 | 'setStatus': [
312 | ('status', str, []),
313 | ('clear', bool, []),
314 | ('status_includes_verb', bool, ['optional']),
315 | ('uid', int, ['optional']),
316 | ],
317 | },
318 |
319 | # events methods
320 | 'events': {
321 | 'get': [
322 | ('uid', int, ['optional']),
323 | ('eids', list, ['optional']),
324 | ('start_time', int, ['optional']),
325 | ('end_time', int, ['optional']),
326 | ('rsvp_status', str, ['optional']),
327 | ],
328 |
329 | 'getMembers': [
330 | ('eid', int, []),
331 | ],
332 |
333 | 'create': [
334 | ('event_info', json, []),
335 | ],
336 | },
337 |
338 | # update methods
339 | 'update': {
340 | 'decodeIDs': [
341 | ('ids', list, []),
342 | ],
343 | },
344 |
345 | # groups methods
346 | 'groups': {
347 | 'get': [
348 | ('uid', int, ['optional']),
349 | ('gids', list, ['optional']),
350 | ],
351 |
352 | 'getMembers': [
353 | ('gid', int, []),
354 | ],
355 | },
356 |
357 | # marketplace methods
358 | 'marketplace': {
359 | 'createListing': [
360 | ('listing_id', int, []),
361 | ('show_on_profile', bool, []),
362 | ('listing_attrs', str, []),
363 | ],
364 |
365 | 'getCategories': [],
366 |
367 | 'getListings': [
368 | ('listing_ids', list, []),
369 | ('uids', list, []),
370 | ],
371 |
372 | 'getSubCategories': [
373 | ('category', str, []),
374 | ],
375 |
376 | 'removeListing': [
377 | ('listing_id', int, []),
378 | ('status', str, []),
379 | ],
380 |
381 | 'search': [
382 | ('category', str, ['optional']),
383 | ('subcategory', str, ['optional']),
384 | ('query', str, ['optional']),
385 | ],
386 | },
387 |
388 | # pages methods
389 | 'pages': {
390 | 'getInfo': [
391 | ('page_ids', list, ['optional']),
392 | ('uid', int, ['optional']),
393 | ],
394 |
395 | 'isAdmin': [
396 | ('page_id', int, []),
397 | ],
398 |
399 | 'isAppAdded': [
400 | ('page_id', int, []),
401 | ],
402 |
403 | 'isFan': [
404 | ('page_id', int, []),
405 | ('uid', int, []),
406 | ],
407 | },
408 |
409 | # photos methods
410 | 'photos': {
411 | 'addTag': [
412 | ('pid', int, []),
413 | ('tag_uid', int, [('default', 0)]),
414 | ('tag_text', str, [('default', '')]),
415 | ('x', float, [('default', 50)]),
416 | ('y', float, [('default', 50)]),
417 | ('tags', str, ['optional']),
418 | ],
419 |
420 | 'createAlbum': [
421 | ('name', str, []),
422 | ('location', str, ['optional']),
423 | ('description', str, ['optional']),
424 | ],
425 |
426 | 'get': [
427 | ('subj_id', int, ['optional']),
428 | ('aid', int, ['optional']),
429 | ('pids', list, ['optional']),
430 | ],
431 |
432 | 'getAlbums': [
433 | ('uid', int, ['optional']),
434 | ('aids', list, ['optional']),
435 | ],
436 |
437 | 'getTags': [
438 | ('pids', list, []),
439 | ],
440 | },
441 |
442 | # fbml methods
443 | 'fbml': {
444 | 'refreshImgSrc': [
445 | ('url', str, []),
446 | ],
447 |
448 | 'refreshRefUrl': [
449 | ('url', str, []),
450 | ],
451 |
452 | 'setRefHandle': [
453 | ('handle', str, []),
454 | ('fbml', str, []),
455 | ],
456 | },
457 |
458 | # SMS Methods
459 | 'sms' : {
460 | 'canSend' : [
461 | ('uid', int, []),
462 | ],
463 |
464 | 'send' : [
465 | ('uid', int, []),
466 | ('message', str, []),
467 | ('session_id', int, []),
468 | ('req_session', bool, []),
469 | ],
470 | },
471 |
472 | 'data': {
473 | 'getCookies': [
474 | ('uid', int, []),
475 | ('string', str, []),
476 | ],
477 |
478 | 'setCookie': [
479 | ('uid', int, []),
480 | ('name', str, []),
481 | ('value', str, []),
482 | ('expires', int, ['optional']),
483 | ('path', str, ['optional']),
484 | ],
485 | },
486 |
487 | # connect methods
488 | 'connect': {
489 | 'registerUsers': [
490 | ('accounts', json, []),
491 | ],
492 |
493 | 'unregisterUsers': [
494 | ('email_hashes', json, []),
495 | ],
496 |
497 | 'getUnconnectedFriendsCount': [
498 | ],
499 | },
500 | }
501 |
502 | class Proxy(object):
503 | """Represents a "namespace" of Facebook API calls."""
504 |
505 | def __init__(self, client, name):
506 | self._client = client
507 | self._name = name
508 |
509 | def __call__(self, method=None, args=None, add_session_args=True):
510 | # for Django templates
511 | if method is None:
512 | return self
513 |
514 | if add_session_args:
515 | self._client._add_session_args(args)
516 |
517 | return self._client('%s.%s' % (self._name, method), args)
518 |
519 |
520 | # generate the Facebook proxies
521 | def __generate_proxies():
522 | for namespace in METHODS:
523 | methods = {}
524 |
525 | for method in METHODS[namespace]:
526 | params = ['self']
527 | body = ['args = {}']
528 |
529 | for param_name, param_type, param_options in METHODS[namespace][method]:
530 | param = param_name
531 |
532 | for option in param_options:
533 | if isinstance(option, tuple) and option[0] == 'default':
534 | if param_type == list:
535 | param = '%s=None' % param_name
536 | body.append('if %s is None: %s = %s' % (param_name, param_name, repr(option[1])))
537 | else:
538 | param = '%s=%s' % (param_name, repr(option[1]))
539 |
540 | if param_type == json:
541 | # we only jsonify the argument if it's a list or a dict, for compatibility
542 | body.append('if isinstance(%s, list) or isinstance(%s, dict): %s = simplejson.dumps(%s)' % ((param_name,) * 4))
543 |
544 | if 'optional' in param_options:
545 | param = '%s=None' % param_name
546 | body.append('if %s is not None: args[\'%s\'] = %s' % (param_name, param_name, param_name))
547 | else:
548 | body.append('args[\'%s\'] = %s' % (param_name, param_name))
549 |
550 | params.append(param)
551 |
552 | # simple docstring to refer them to Facebook API docs
553 | body.insert(0, '"""Facebook API call. See http://developers.facebook.com/documentation.php?v=1.0&method=%s.%s"""' % (namespace, method))
554 |
555 | body.insert(0, 'def %s(%s):' % (method, ', '.join(params)))
556 |
557 | body.append('return self(\'%s\', args)' % method)
558 |
559 | exec('\n '.join(body))
560 |
561 | methods[method] = eval(method)
562 |
563 | proxy = type('%sProxy' % namespace.title(), (Proxy, ), methods)
564 |
565 | globals()[proxy.__name__] = proxy
566 |
567 |
568 | __generate_proxies()
569 |
570 |
571 | class FacebookError(Exception):
572 | """Exception class for errors received from Facebook."""
573 |
574 | def __init__(self, code, msg, args=None):
575 | self.code = code
576 | self.msg = msg
577 | self.args = args
578 |
579 | def __str__(self):
580 | return 'Error %s: %s' % (self.code, self.msg)
581 |
582 |
583 | class AuthProxy(Proxy):
584 | """Special proxy for facebook.auth."""
585 |
586 | def getSession(self):
587 | """Facebook API call. See http://developers.facebook.com/documentation.php?v=1.0&method=auth.getSession"""
588 | args = {}
589 | try:
590 | args['auth_token'] = self._client.auth_token
591 | except AttributeError:
592 | raise RuntimeError('Client does not have auth_token set.')
593 | result = self._client('%s.getSession' % self._name, args)
594 | self._client.session_key = result['session_key']
595 | self._client.uid = result['uid']
596 | self._client.secret = result.get('secret')
597 | self._client.session_key_expires = result['expires']
598 | return result
599 |
600 | def createToken(self):
601 | """Facebook API call. See http://developers.facebook.com/documentation.php?v=1.0&method=auth.createToken"""
602 | token = self._client('%s.createToken' % self._name)
603 | self._client.auth_token = token
604 | return token
605 |
606 |
607 | class FriendsProxy(FriendsProxy):
608 | """Special proxy for facebook.friends."""
609 |
610 | def get(self, **kwargs):
611 | """Facebook API call. See http://developers.facebook.com/documentation.php?v=1.0&method=friends.get"""
612 | if not kwargs.get('flid') and self._client._friends:
613 | return self._client._friends
614 | return super(FriendsProxy, self).get(**kwargs)
615 |
616 |
617 | class PhotosProxy(PhotosProxy):
618 | """Special proxy for facebook.photos."""
619 |
620 | def upload(self, image, aid=None, caption=None, size=(604, 1024), filename=None):
621 | """Facebook API call. See http://developers.facebook.com/documentation.php?v=1.0&method=photos.upload
622 |
623 | size -- an optional size (width, height) to resize the image to before uploading. Resizes by default
624 | to Facebook's maximum display width of 604.
625 | """
626 | args = {}
627 |
628 | if aid is not None:
629 | args['aid'] = aid
630 |
631 | if caption is not None:
632 | args['caption'] = caption
633 |
634 | args = self._client._build_post_args('facebook.photos.upload', self._client._add_session_args(args))
635 |
636 | try:
637 | import cStringIO as StringIO
638 | except ImportError:
639 | import StringIO
640 |
641 | # check for a filename specified...if the user is passing binary data in
642 | # image then a filename will be specified
643 | if filename is None:
644 | try:
645 | import Image
646 | except ImportError:
647 | data = StringIO.StringIO(open(image, 'rb').read())
648 | else:
649 | img = Image.open(image)
650 | if size:
651 | img.thumbnail(size, Image.ANTIALIAS)
652 | data = StringIO.StringIO()
653 | img.save(data, img.format)
654 | else:
655 | # there was a filename specified, which indicates that image was not
656 | # the path to an image file but rather the binary data of a file
657 | data = StringIO.StringIO(image)
658 | image = filename
659 |
660 | content_type, body = self.__encode_multipart_formdata(list(args.iteritems()), [(image, data)])
661 | urlinfo = urlparse.urlsplit(self._client.facebook_url)
662 | try:
663 | h = httplib.HTTP(urlinfo[1])
664 | h.putrequest('POST', urlinfo[2])
665 | h.putheader('Content-Type', content_type)
666 | h.putheader('Content-Length', str(len(body)))
667 | h.putheader('MIME-Version', '1.0')
668 | h.putheader('User-Agent', 'PyFacebook Client Library')
669 | h.endheaders()
670 | h.send(body)
671 |
672 | reply = h.getreply()
673 |
674 | if reply[0] != 200:
675 | raise Exception('Error uploading photo: Facebook returned HTTP %s (%s)' % (reply[0], reply[1]))
676 |
677 | response = h.file.read()
678 | except:
679 | # sending the photo failed, perhaps we are using GAE
680 | try:
681 | from google.appengine.api import urlfetch
682 |
683 | try:
684 | response = urlread(url=self._client.facebook_url,data=body,headers={'POST':urlinfo[2],'Content-Type':content_type,'MIME-Version':'1.0'})
685 | except urllib2.URLError:
686 | raise Exception('Error uploading photo: Facebook returned %s' % (response))
687 | except ImportError:
688 | # could not import from google.appengine.api, so we are not running in GAE
689 | raise Exception('Error uploading photo.')
690 |
691 | return self._client._parse_response(response, 'facebook.photos.upload')
692 |
693 |
694 | def __encode_multipart_formdata(self, fields, files):
695 | """Encodes a multipart/form-data message to upload an image."""
696 | boundary = '-------tHISiStheMulTIFoRMbOUNDaRY'
697 | crlf = '\r\n'
698 | l = []
699 |
700 | for (key, value) in fields:
701 | l.append('--' + boundary)
702 | l.append('Content-Disposition: form-data; name="%s"' % str(key))
703 | l.append('')
704 | l.append(str(value))
705 | for (filename, value) in files:
706 | l.append('--' + boundary)
707 | l.append('Content-Disposition: form-data; filename="%s"' % (str(filename), ))
708 | l.append('Content-Type: %s' % self.__get_content_type(filename))
709 | l.append('')
710 | l.append(value.getvalue())
711 | l.append('--' + boundary + '--')
712 | l.append('')
713 | body = crlf.join(l)
714 | content_type = 'multipart/form-data; boundary=%s' % boundary
715 | return content_type, body
716 |
717 |
718 | def __get_content_type(self, filename):
719 | """Returns a guess at the MIME type of the file from the filename."""
720 | return str(mimetypes.guess_type(filename)[0]) or 'application/octet-stream'
721 |
722 |
723 | class Facebook(object):
724 | """
725 | Provides access to the Facebook API.
726 |
727 | Instance Variables:
728 |
729 | added
730 | True if the user has added this application.
731 |
732 | api_key
733 | Your API key, as set in the constructor.
734 |
735 | app_name
736 | Your application's name, i.e. the APP_NAME in http://apps.facebook.com/APP_NAME/ if
737 | this is for an internal web application. Optional, but useful for automatic redirects
738 | to canvas pages.
739 |
740 | auth_token
741 | The auth token that Facebook gives you, either with facebook.auth.createToken,
742 | or through a GET parameter.
743 |
744 | callback_path
745 | The path of the callback set in the Facebook app settings. If your callback is set
746 | to http://www.example.com/facebook/callback/, this should be '/facebook/callback/'.
747 | Optional, but useful for automatic redirects back to the same page after login.
748 |
749 | desktop
750 | True if this is a desktop app, False otherwise. Used for determining how to
751 | authenticate.
752 |
753 | facebook_url
754 | The url to use for Facebook requests.
755 |
756 | facebook_secure_url
757 | The url to use for secure Facebook requests.
758 |
759 | in_canvas
760 | True if the current request is for a canvas page.
761 |
762 | internal
763 | True if this Facebook object is for an internal application (one that can be added on Facebook)
764 |
765 | page_id
766 | Set to the page_id of the current page (if any)
767 |
768 | secret
769 | Secret that is used after getSession for desktop apps.
770 |
771 | secret_key
772 | Your application's secret key, as set in the constructor.
773 |
774 | session_key
775 | The current session key. Set automatically by auth.getSession, but can be set
776 | manually for doing infinite sessions.
777 |
778 | session_key_expires
779 | The UNIX time of when this session key expires, or 0 if it never expires.
780 |
781 | uid
782 | After a session is created, you can get the user's UID with this variable. Set
783 | automatically by auth.getSession.
784 |
785 | ----------------------------------------------------------------------
786 |
787 | """
788 |
789 | def __init__(self, api_key, secret_key, auth_token=None, app_name=None, callback_path=None, internal=None, proxy=None, facebook_url=None, facebook_secure_url=None):
790 | """
791 | Initializes a new Facebook object which provides wrappers for the Facebook API.
792 |
793 | If this is a desktop application, the next couple of steps you might want to take are:
794 |
795 | facebook.auth.createToken() # create an auth token
796 | facebook.login() # show a browser window
797 | wait_login() # somehow wait for the user to log in
798 | facebook.auth.getSession() # get a session key
799 |
800 | For web apps, if you are passed an auth_token from Facebook, pass that in as a named parameter.
801 | Then call:
802 |
803 | facebook.auth.getSession()
804 |
805 | """
806 | self.api_key = api_key
807 | self.secret_key = secret_key
808 | self.session_key = None
809 | self.session_key_expires = None
810 | self.auth_token = auth_token
811 | self.secret = None
812 | self.uid = None
813 | self.page_id = None
814 | self.in_canvas = False
815 | self.added = False
816 | self.app_name = app_name
817 | self.callback_path = callback_path
818 | self.internal = internal
819 | self._friends = None
820 | self.proxy = proxy
821 | if facebook_url is None:
822 | self.facebook_url = FACEBOOK_URL
823 | else:
824 | self.facebook_url = facebook_url
825 | if facebook_secure_url is None:
826 | self.facebook_secure_url = FACEBOOK_SECURE_URL
827 | else:
828 | self.facebook_secure_url = facebook_secure_url
829 |
830 | for namespace in METHODS:
831 | self.__dict__[namespace] = eval('%sProxy(self, \'%s\')' % (namespace.title(), 'facebook.%s' % namespace))
832 |
833 | self.auth = AuthProxy(self, 'facebook.auth')
834 |
835 |
836 | def _hash_args(self, args, secret=None):
837 | """Hashes arguments by joining key=value pairs, appending a secret, and then taking the MD5 hex digest."""
838 | # @author: houyr
839 | # fix for UnicodeEncodeError
840 | hasher = md5.new(''.join(['%s=%s' % (isinstance(x, unicode) and x.encode("utf-8") or x, isinstance(args[x], unicode) and args[x].encode("utf-8") or args[x]) for x in sorted(args.keys())]))
841 | if secret:
842 | hasher.update(secret)
843 | elif self.secret:
844 | hasher.update(self.secret)
845 | else:
846 | hasher.update(self.secret_key)
847 | return hasher.hexdigest()
848 |
849 |
850 | def _parse_response_item(self, node):
851 | """Parses an XML response node from Facebook."""
852 | if node.nodeType == node.DOCUMENT_NODE and \
853 | node.childNodes[0].hasAttributes() and \
854 | node.childNodes[0].hasAttribute('list') and \
855 | node.childNodes[0].getAttribute('list') == "true":
856 | return {node.childNodes[0].nodeName: self._parse_response_list(node.childNodes[0])}
857 | elif node.nodeType == node.ELEMENT_NODE and \
858 | node.hasAttributes() and \
859 | node.hasAttribute('list') and \
860 | node.getAttribute('list')=="true":
861 | return self._parse_response_list(node)
862 | elif len(filter(lambda x: x.nodeType == x.ELEMENT_NODE, node.childNodes)) > 0:
863 | return self._parse_response_dict(node)
864 | else:
865 | return ''.join(node.data for node in node.childNodes if node.nodeType == node.TEXT_NODE)
866 |
867 |
868 | def _parse_response_dict(self, node):
869 | """Parses an XML dictionary response node from Facebook."""
870 | result = {}
871 | for item in filter(lambda x: x.nodeType == x.ELEMENT_NODE, node.childNodes):
872 | result[item.nodeName] = self._parse_response_item(item)
873 | if node.nodeType == node.ELEMENT_NODE and node.hasAttributes():
874 | if node.hasAttribute('id'):
875 | result['id'] = node.getAttribute('id')
876 | return result
877 |
878 |
879 | def _parse_response_list(self, node):
880 | """Parses an XML list response node from Facebook."""
881 | result = []
882 | for item in filter(lambda x: x.nodeType == x.ELEMENT_NODE, node.childNodes):
883 | result.append(self._parse_response_item(item))
884 | return result
885 |
886 |
887 | def _check_error(self, response):
888 | """Checks if the given Facebook response is an error, and then raises the appropriate exception."""
889 | if type(response) is dict and response.has_key('error_code'):
890 | raise FacebookError(response['error_code'], response['error_msg'], response['request_args'])
891 |
892 |
893 | def _build_post_args(self, method, args=None):
894 | """Adds to args parameters that are necessary for every call to the API."""
895 | if args is None:
896 | args = {}
897 |
898 | for arg in args.items():
899 | if type(arg[1]) == list:
900 | args[arg[0]] = ','.join(str(a) for a in arg[1])
901 | elif type(arg[1]) == unicode:
902 | args[arg[0]] = arg[1].encode("UTF-8")
903 | elif type(arg[1]) == bool:
904 | args[arg[0]] = str(arg[1]).lower()
905 |
906 | args['method'] = method
907 | args['api_key'] = self.api_key
908 | args['v'] = '1.0'
909 | args['format'] = RESPONSE_FORMAT
910 | args['sig'] = self._hash_args(args)
911 |
912 | return args
913 |
914 |
915 | def _add_session_args(self, args=None):
916 | """Adds 'session_key' and 'call_id' to args, which are used for API calls that need sessions."""
917 | if args is None:
918 | args = {}
919 |
920 | if not self.session_key:
921 | return args
922 | #some calls don't need a session anymore. this might be better done in the markup
923 | #raise RuntimeError('Session key not set. Make sure auth.getSession has been called.')
924 |
925 | args['session_key'] = self.session_key
926 | args['call_id'] = str(int(time.time() * 1000))
927 |
928 | return args
929 |
930 |
931 | def _parse_response(self, response, method, format=None):
932 | """Parses the response according to the given (optional) format, which should be either 'JSON' or 'XML'."""
933 | if not format:
934 | format = RESPONSE_FORMAT
935 |
936 | if format == 'JSON':
937 | result = simplejson.loads(response)
938 |
939 | self._check_error(result)
940 | elif format == 'XML':
941 | dom = minidom.parseString(response)
942 | result = self._parse_response_item(dom)
943 | dom.unlink()
944 |
945 | if 'error_response' in result:
946 | self._check_error(result['error_response'])
947 |
948 | result = result[method[9:].replace('.', '_') + '_response']
949 | else:
950 | raise RuntimeError('Invalid format specified.')
951 |
952 | return result
953 |
954 |
955 | def hash_email(self, email):
956 | """
957 | Hash an email address in a format suitable for Facebook Connect.
958 |
959 | """
960 | email = email.lower().strip()
961 | return "%s_%s" % (
962 | struct.unpack("I", struct.pack("i", binascii.crc32(email)))[0],
963 | hashlib.md5(email).hexdigest(),
964 | )
965 |
966 |
967 | def unicode_urlencode(self, params):
968 | """
969 | @author: houyr
970 | A unicode aware version of urllib.urlencode.
971 | """
972 | if isinstance(params, dict):
973 | params = params.items()
974 | return urllib.urlencode([(k, isinstance(v, unicode) and v.encode('utf-8') or v)
975 | for k, v in params])
976 |
977 |
978 | def __call__(self, method=None, args=None, secure=False):
979 | """Make a call to Facebook's REST server."""
980 | # for Django templates, if this object is called without any arguments
981 | # return the object itself
982 | if method is None:
983 | return self
984 |
985 | # @author: houyr
986 | # fix for bug of UnicodeEncodeError
987 | post_data = self.unicode_urlencode(self._build_post_args(method, args))
988 |
989 | if self.proxy:
990 | proxy_handler = urllib2.ProxyHandler(self.proxy)
991 | opener = urllib2.build_opener(proxy_handler)
992 | if secure:
993 | response = opener.open(self.facebook_secure_url, post_data).read()
994 | else:
995 | response = opener.open(self.facebook_url, post_data).read()
996 | else:
997 | if secure:
998 | response = urlread(self.facebook_secure_url, post_data)
999 | else:
1000 | response = urlread(self.facebook_url, post_data)
1001 |
1002 | return self._parse_response(response, method)
1003 |
1004 |
1005 | # URL helpers
1006 | def get_url(self, page, **args):
1007 | """
1008 | Returns one of the Facebook URLs (www.facebook.com/SOMEPAGE.php).
1009 | Named arguments are passed as GET query string parameters.
1010 |
1011 | """
1012 | return 'http://www.facebook.com/%s.php?%s' % (page, urllib.urlencode(args))
1013 |
1014 |
1015 | def get_app_url(self, path=''):
1016 | """
1017 | Returns the URL for this app's canvas page, according to app_name.
1018 |
1019 | """
1020 | return 'http://apps.facebook.com/%s/%s' % (self.app_name, path)
1021 |
1022 |
1023 | def get_add_url(self, next=None):
1024 | """
1025 | Returns the URL that the user should be redirected to in order to add the application.
1026 |
1027 | """
1028 | args = {'api_key': self.api_key, 'v': '1.0'}
1029 |
1030 | if next is not None:
1031 | args['next'] = next
1032 |
1033 | return self.get_url('install', **args)
1034 |
1035 |
1036 | def get_authorize_url(self, next=None, next_cancel=None):
1037 | """
1038 | Returns the URL that the user should be redirected to in order to
1039 | authorize certain actions for application.
1040 |
1041 | """
1042 | args = {'api_key': self.api_key, 'v': '1.0'}
1043 |
1044 | if next is not None:
1045 | args['next'] = next
1046 |
1047 | if next_cancel is not None:
1048 | args['next_cancel'] = next_cancel
1049 |
1050 | return self.get_url('authorize', **args)
1051 |
1052 |
1053 | def get_login_url(self, next=None, popup=False, canvas=True):
1054 | """
1055 | Returns the URL that the user should be redirected to in order to login.
1056 |
1057 | next -- the URL that Facebook should redirect to after login
1058 |
1059 | """
1060 | args = {'api_key': self.api_key, 'v': '1.0'}
1061 |
1062 | if next is not None:
1063 | args['next'] = next
1064 |
1065 | if canvas is True:
1066 | args['canvas'] = 1
1067 |
1068 | if popup is True:
1069 | args['popup'] = 1
1070 |
1071 | if self.auth_token is not None:
1072 | args['auth_token'] = self.auth_token
1073 |
1074 | return self.get_url('login', **args)
1075 |
1076 |
1077 | def login(self, popup=False):
1078 | """Open a web browser telling the user to login to Facebook."""
1079 | import webbrowser
1080 | webbrowser.open(self.get_login_url(popup=popup))
1081 |
1082 |
1083 | def get_ext_perm_url(self, ext_perm, next=None, popup=False):
1084 | """
1085 | Returns the URL that the user should be redirected to in order to grant an extended permission.
1086 |
1087 | ext_perm -- the name of the extended permission to request
1088 | next -- the URL that Facebook should redirect to after login
1089 |
1090 | """
1091 | args = {'ext_perm': ext_perm, 'api_key': self.api_key, 'v': '1.0'}
1092 |
1093 | if next is not None:
1094 | args['next'] = next
1095 |
1096 | if popup is True:
1097 | args['popup'] = 1
1098 |
1099 | return self.get_url('authorize', **args)
1100 |
1101 |
1102 | def request_extended_permission(self, ext_perm, popup=False):
1103 | """Open a web browser telling the user to grant an extended permission."""
1104 | import webbrowser
1105 | webbrowser.open(self.get_ext_perm_url(ext_perm, popup=popup))
1106 |
1107 |
1108 | def check_session(self, request):
1109 | """
1110 | Checks the given Django HttpRequest for Facebook parameters such as
1111 | POST variables or an auth token. If the session is valid, returns True
1112 | and this object can now be used to access the Facebook API. Otherwise,
1113 | it returns False, and the application should take the appropriate action
1114 | (either log the user in or have him add the application).
1115 |
1116 | """
1117 | self.in_canvas = (request.POST.get('fb_sig_in_canvas') == '1')
1118 |
1119 | if self.session_key and (self.uid or self.page_id):
1120 | return True
1121 |
1122 | if request.method == 'POST':
1123 | params = self.validate_signature(request.POST)
1124 | else:
1125 | if 'installed' in request.GET:
1126 | self.added = True
1127 |
1128 | if 'fb_page_id' in request.GET:
1129 | self.page_id = request.GET['fb_page_id']
1130 |
1131 | if 'auth_token' in request.GET:
1132 | self.auth_token = request.GET['auth_token']
1133 |
1134 | try:
1135 | self.auth.getSession()
1136 | except FacebookError, e:
1137 | self.auth_token = None
1138 | return False
1139 |
1140 | return True
1141 |
1142 | params = self.validate_signature(request.GET)
1143 |
1144 | if not params:
1145 | # first check if we are in django - to check cookies
1146 | if hasattr(request, 'COOKIES'):
1147 | params = self.validate_cookie_signature(request.COOKIES)
1148 | else:
1149 | # if not, then we might be on GoogleAppEngine, check their request object cookies
1150 | if hasattr(request,'cookies'):
1151 | params = self.validate_cookie_signature(request.cookies)
1152 |
1153 | if not params:
1154 | return False
1155 |
1156 | if params.get('in_canvas') == '1':
1157 | self.in_canvas = True
1158 |
1159 | if params.get('added') == '1':
1160 | self.added = True
1161 |
1162 | if params.get('expires'):
1163 | self.session_key_expires = int(params['expires'])
1164 |
1165 | if 'friends' in params:
1166 | if params['friends']:
1167 | self._friends = params['friends'].split(',')
1168 | else:
1169 | self._friends = []
1170 |
1171 | if 'session_key' in params:
1172 | self.session_key = params['session_key']
1173 | if 'user' in params:
1174 | self.uid = params['user']
1175 | elif 'page_id' in params:
1176 | self.page_id = params['page_id']
1177 | else:
1178 | return False
1179 | elif 'profile_session_key' in params:
1180 | self.session_key = params['profile_session_key']
1181 | if 'profile_user' in params:
1182 | self.uid = params['profile_user']
1183 | else:
1184 | return False
1185 | else:
1186 | return False
1187 |
1188 | return True
1189 |
1190 |
1191 | def validate_signature(self, post, prefix='fb_sig', timeout=None):
1192 | """
1193 | Validate parameters passed to an internal Facebook app from Facebook.
1194 |
1195 | """
1196 | args = post.copy()
1197 |
1198 | if prefix not in args:
1199 | return None
1200 |
1201 | del args[prefix]
1202 |
1203 | if timeout and '%s_time' % prefix in post and time.time() - float(post['%s_time' % prefix]) > timeout:
1204 | return None
1205 |
1206 | args = dict([(key[len(prefix + '_'):], value) for key, value in args.items() if key.startswith(prefix)])
1207 |
1208 | hash = self._hash_args(args)
1209 |
1210 | if hash == post[prefix]:
1211 | return args
1212 | else:
1213 | return None
1214 |
1215 | def validate_cookie_signature(self, cookies):
1216 | """
1217 | Validate parameters passed by cookies, namely facebookconnect or js api.
1218 | """
1219 | if not self.api_key in cookies.keys():
1220 | return None
1221 |
1222 | sigkeys = []
1223 | params = dict()
1224 | for k in sorted(cookies.keys()):
1225 | if k.startswith(self.api_key+"_"):
1226 | sigkeys.append(k)
1227 | params[k.replace(self.api_key+"_","")] = cookies[k]
1228 |
1229 |
1230 | vals = ''.join(['%s=%s' % (x.replace(self.api_key+"_",""), cookies[x]) for x in sigkeys])
1231 | hasher = md5.new(vals)
1232 |
1233 | hasher.update(self.secret_key)
1234 | digest = hasher.hexdigest()
1235 | if digest == cookies[self.api_key]:
1236 | return params
1237 | else:
1238 | return False
1239 |
1240 |
1241 |
1242 |
1243 | if __name__ == '__main__':
1244 | # sample desktop application
1245 |
1246 | api_key = ''
1247 | secret_key = ''
1248 |
1249 | facebook = Facebook(api_key, secret_key)
1250 |
1251 | facebook.auth.createToken()
1252 |
1253 | # Show login window
1254 | # Set popup=True if you want login without navigational elements
1255 | facebook.login()
1256 |
1257 | # Login to the window, then press enter
1258 | print 'After logging in, press enter...'
1259 | raw_input()
1260 |
1261 | facebook.auth.getSession()
1262 | print 'Session Key: ', facebook.session_key
1263 | print 'Your UID: ', facebook.uid
1264 |
1265 | info = facebook.users.getInfo([facebook.uid], ['name', 'birthday', 'affiliations', 'sex'])[0]
1266 |
1267 | print 'Your Name: ', info['name']
1268 | print 'Your Birthday: ', info['birthday']
1269 | print 'Your Gender: ', info['sex']
1270 |
1271 | friends = facebook.friends.get()
1272 | friends = facebook.users.getInfo(friends[0:5], ['name', 'birthday', 'relationship_status'])
1273 |
1274 | for friend in friends:
1275 | print friend['name'], 'has a birthday on', friend['birthday'], 'and is', friend['relationship_status']
1276 |
1277 | arefriends = facebook.friends.areFriends([friends[0]['uid']], [friends[1]['uid']])
1278 |
1279 | photos = facebook.photos.getAlbums(facebook.uid)
1280 |
1281 |
--------------------------------------------------------------------------------