├── examples
├── newsfeed
│ ├── static
│ │ ├── robots.txt
│ │ ├── favicon.ico
│ │ └── base.css
│ ├── app.yaml
│ ├── templates
│ │ ├── index.html
│ │ ├── base.html
│ │ └── home.html
│ └── facebookclient.py
├── appengine
│ ├── app.yaml
│ ├── example.html
│ └── example.py
├── oauth
│ ├── app.yaml
│ ├── oauth.html
│ └── facebookoauth.py
└── tornado
│ ├── schema.sql
│ ├── example.html
│ └── example.py
├── setup.py
├── readme.md
└── src
└── facebook.py
/examples/newsfeed/static/robots.txt:
--------------------------------------------------------------------------------
1 | User-Agent: *
2 | Disallow: /
3 |
--------------------------------------------------------------------------------
/examples/newsfeed/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buzz/python-sdk/master/examples/newsfeed/static/favicon.ico
--------------------------------------------------------------------------------
/examples/appengine/app.yaml:
--------------------------------------------------------------------------------
1 | application: facebook-example
2 | version: 1
3 | runtime: python
4 | api_version: 1
5 |
6 | handlers:
7 | - url: /.*
8 | script: example.py
9 |
--------------------------------------------------------------------------------
/examples/oauth/app.yaml:
--------------------------------------------------------------------------------
1 | application: facebook-example
2 | version: 1
3 | runtime: python
4 | api_version: 1
5 |
6 | handlers:
7 | - url: /.*
8 | script: facebookoauth.py
9 |
--------------------------------------------------------------------------------
/examples/newsfeed/app.yaml:
--------------------------------------------------------------------------------
1 | application: facebook-example
2 | version: 1
3 | runtime: python
4 | api_version: 1
5 |
6 | handlers:
7 | - url: /static
8 | static_dir: static
9 |
10 | - url: /favicon\.ico
11 | static_files: static/favicon.ico
12 | upload: static/favicon.ico
13 |
14 | - url: /robots\.txt
15 | static_files: static/robots.txt
16 | upload: static/robots.txt
17 |
18 | - url: /.*
19 | script: facebookclient.py
20 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | from distutils.core import setup
4 |
5 | setup(
6 | name='facebook-python-sdk',
7 | version='0.1',
8 | description='This client library is designed to support the Facebook Graph API and the official Facebook JavaScript SDK, which is the canonical way to implement Facebook authentication.',
9 | author='Facebook',
10 | url='http://github.com/facebook/python-sdk',
11 | package_dir={'': 'src'},
12 | py_modules=[
13 | 'facebook',
14 | ],
15 | )
16 |
--------------------------------------------------------------------------------
/examples/newsfeed/templates/index.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block body %}
4 |
This application is a simple Facebook client. It shows you your News Feed and enables you to post status messages back to your profile. It is designed to demonstrate the use of the Facebook Graph API , the core part of the Facebook Platform . To get started, log in to Facebook below:
5 |
6 | You can download the source code to this application on GitHub .
7 | {% endblock %}
8 |
--------------------------------------------------------------------------------
/examples/oauth/oauth.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Facebook OAuth Example
6 |
7 |
8 | {% if current_user %}
9 |
10 | You are logged in as {{ current_user.name|escape }}
11 | Log out
12 | {% else %}
13 | You are not yet logged into this site
14 | Log in with Facebook
15 | {% endif %}
16 |
17 |
18 |
--------------------------------------------------------------------------------
/examples/tornado/schema.sql:
--------------------------------------------------------------------------------
1 | -- Copyright 2010 Facebook
2 | --
3 | -- Licensed under the Apache License, Version 2.0 (the "License"); you may
4 | -- not use this file except in compliance with the License. You may obtain
5 | -- a copy of the License at
6 | --
7 | -- http://www.apache.org/licenses/LICENSE-2.0
8 | --
9 | -- Unless required by applicable law or agreed to in writing, software
10 | -- distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 | -- WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 | -- License for the specific language governing permissions and limitations
13 | -- under the License.
14 |
15 | -- To create the database:
16 | -- CREATE DATABASE example;
17 | -- GRANT ALL PRIVILEGES ON example.* TO 'example'@'localhost' IDENTIFIED BY 'example';
18 | --
19 | -- To reload the tables:
20 | -- mysql --user=example --password=example --database=example < schema.sql
21 |
22 | SET SESSION storage_engine = "InnoDB";
23 | SET SESSION time_zone = "+0:00";
24 | ALTER DATABASE CHARACTER SET "utf8";
25 |
26 | DROP TABLE IF EXISTS users;
27 | CREATE TABLE users (
28 | id VARCHAR(25) NOT NULL PRIMARY KEY,
29 | name VARCHAR(256) NOT NULL,
30 | profile_url VARCHAR(512) NOT NULL,
31 | access_token VARCHAR(512) NOT NULL,
32 | updated TIMESTAMP NOT NULL
33 | );
34 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | Facebook Python SDK
2 | ====
3 |
4 | This client library is designed to support the
5 | [Facebook Graph API](http://developers.facebook.com/docs/api) and the official
6 | [Facebook JavaScript SDK](http://github.com/facebook/connect-js), which is
7 | the canonical way to implement Facebook authentication. You can read more
8 | about the Graph API at [http://developers.facebook.com/docs/api](http://developers.facebook.com/docs/api).
9 |
10 | Basic usage:
11 |
12 | graph = facebook.GraphAPI(oauth_access_token)
13 | profile = graph.get_object("me")
14 | friends = graph.get_connections("me", "friends")
15 | graph.put_object("me", "feed", message="I am writing on my wall!")
16 |
17 | If you are using the module within a web application with the
18 | [JavaScript SDK](http://github.com/facebook/connect-js), you can also use the
19 | module to use Facebook for login, parsing the cookie set by the JavaScript SDK
20 | for logged in users. For example, in Google AppEngine, you could get the
21 | profile of the logged in user with:
22 |
23 | user = facebook.get_user_from_cookie(self.request.cookies, key, secret)
24 | if user:
25 | graph = facebook.GraphAPI(user["oauth_access_token"])
26 | profile = graph.get_object("me")
27 | friends = graph.get_connections("me", "friends")
28 |
29 | You can see a full AppEngine example application in examples/appengine.
30 |
--------------------------------------------------------------------------------
/examples/appengine/example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Facebook Example
6 |
7 |
8 |
9 |
10 | {% if current_user %}
11 |
12 | Hello, {{ current_user.name|escape }}
13 | {% endif %}
14 |
15 |
16 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/examples/tornado/example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Facebook Example
6 |
7 |
8 |
9 |
10 | {% if current_user %}
11 |
12 | Hello, {{ escape(current_user.name) }}
13 | {% end %}
14 |
15 |
16 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/examples/newsfeed/templates/base.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {% block title %}Facebook Client Example{% endblock %}
6 |
7 | {% block head %}{% endblock %}
8 |
9 |
10 |
11 | {% block body %}{% endblock %}
12 |
13 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/examples/newsfeed/templates/home.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block body %}
4 |
8 |
9 |
10 |
19 |
20 | {% for post in news_feed.data %}
21 |
22 |
23 |
24 |
28 | {% if post.caption or post.picture %}
29 |
30 | {% if post.picture %}
31 |
32 | {% endif %}
33 | {% if post.name %}
34 |
35 | {% endif %}
36 | {% if post.caption %}
37 |
{{ post.caption|escape }}
38 | {% endif %}
39 | {% if post.description %}
40 |
{{ post.description|escape }}
41 | {% endif %}
42 |
43 | {% endif %}
44 |
45 | {{ post.created_time|timesince }} ago
46 |
47 |
48 |
49 | {% endfor %}
50 |
51 |
52 | {% endblock %}
53 |
--------------------------------------------------------------------------------
/examples/newsfeed/static/base.css:
--------------------------------------------------------------------------------
1 | body {
2 | background: white;
3 | margin: 0;
4 | }
5 |
6 | body,
7 | input,
8 | textarea {
9 | color: #333;
10 | font-family: "Lucida Grande", Tahoma, Verdana, Arial, sans-serif;
11 | font-size: 13px;
12 | }
13 |
14 | a {
15 | color: #3b5998;
16 | text-decoration: none;
17 | }
18 |
19 | a:hover {
20 | text-decoration: underline;
21 | }
22 |
23 | img {
24 | border: 0;
25 | }
26 |
27 | table {
28 | border: 0;
29 | border-collapse: collapse;
30 | border-spacing: 0;
31 | }
32 |
33 | td {
34 | border: 0;
35 | padding: 0;
36 | }
37 |
38 | #promo {
39 | background: #eee;
40 | padding: 8px;
41 | border-bottom: 1px solid #ccc;
42 | color: gray;
43 | text-align: center;
44 | }
45 |
46 | #body {
47 | max-width: 800px;
48 | margin: auto;
49 | padding: 20px;
50 | }
51 |
52 | #header h1 {
53 | margin: 0;
54 | padding: 0;
55 | font-size: 15px;
56 | line-height: 25px;
57 | }
58 |
59 | #header .button {
60 | float: right;
61 | }
62 |
63 | #content {
64 | clear: both;
65 | margin-top: 15px;
66 | }
67 |
68 | .clearfix:after {
69 | clear: both;
70 | content: ".";
71 | display: block;
72 | font-size: 0;
73 | height: 0;
74 | line-height: 0;
75 | visibility: hidden;
76 | }
77 |
78 | .clearfix {
79 | display: block;
80 | zoom: 1;
81 | }
82 |
83 | .feed .entry {
84 | padding-top: 9px;
85 | border-top: 1px solid #eee;
86 | margin-top: 9px;
87 | }
88 |
89 | .feed .entry .profile {
90 | float: left;
91 | line-height: 0;
92 | }
93 |
94 | .feed .entry .profile img {
95 | width: 50px;
96 | height: 50px;
97 | }
98 |
99 | .feed .entry .body {
100 | margin-left: 60px;
101 | }
102 |
103 | .feed .entry .name {
104 | font-weight: bold;
105 | }
106 |
107 | .feed .entry .attachment {
108 | font-size: 11px;
109 | line-height: 15px;
110 | margin-top: 8px;
111 | margin-bottom: 8px;
112 | color: gray;
113 | }
114 |
115 | .feed .entry .attachment.nopicture {
116 | border-left: 2px solid #ccc;
117 | padding-left: 10px;
118 | }
119 |
120 | .feed .entry .attachment .picture {
121 | line-height: 0;
122 | float: left;
123 | padding-right: 10px;
124 | }
125 |
126 | .feed .entry .attachment .picture img {
127 | border: 1px solid #ccc;
128 | padding: 3px;
129 | }
130 |
131 | .feed .entry .attachment .picture a:hover img {
132 | border-color: #3b5998;
133 | }
134 |
135 | .feed .entry .info {
136 | font-size: 11px;
137 | line-height: 17px;
138 | margin-top: 3px;
139 | color: gray;
140 | }
141 |
142 | .feed .entry .info.icon {
143 | background-position: left center;
144 | background-repeat: no-repeat;
145 | padding-left: 20px;
146 | }
147 |
148 | .feed .post .textbox {
149 | margin-right: 6px;
150 | }
151 |
152 | .feed .post .textbox textarea {
153 | margin: 0;
154 | border: 1px solid #bbb;
155 | border-top-color: #aeaeae;
156 | padding: 2px;
157 | width: 100%;
158 | }
159 |
160 | .feed .post .buttons {
161 | text-align: right;
162 | }
163 |
--------------------------------------------------------------------------------
/examples/appengine/example.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Copyright 2010 Facebook
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License. You may obtain
7 | # a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 | # License for the specific language governing permissions and limitations
15 | # under the License.
16 |
17 | """A barebones AppEngine application that uses Facebook for login."""
18 |
19 | FACEBOOK_APP_ID = "your app id"
20 | FACEBOOK_APP_SECRET = "your app secret"
21 |
22 | import facebook
23 | import os.path
24 | import wsgiref.handlers
25 |
26 | from google.appengine.ext import db
27 | from google.appengine.ext import webapp
28 | from google.appengine.ext.webapp import util
29 | from google.appengine.ext.webapp import template
30 |
31 |
32 | class User(db.Model):
33 | id = db.StringProperty(required=True)
34 | created = db.DateTimeProperty(auto_now_add=True)
35 | updated = db.DateTimeProperty(auto_now=True)
36 | name = db.StringProperty(required=True)
37 | profile_url = db.StringProperty(required=True)
38 | access_token = db.StringProperty(required=True)
39 |
40 |
41 | class BaseHandler(webapp.RequestHandler):
42 | """Provides access to the active Facebook user in self.current_user
43 |
44 | The property is lazy-loaded on first access, using the cookie saved
45 | by the Facebook JavaScript SDK to determine the user ID of the active
46 | user. See http://developers.facebook.com/docs/authentication/ for
47 | more information.
48 | """
49 | @property
50 | def current_user(self):
51 | if not hasattr(self, "_current_user"):
52 | self._current_user = None
53 | cookie = facebook.get_user_from_cookie(
54 | self.request.cookies, FACEBOOK_APP_ID, FACEBOOK_APP_SECRET)
55 | if cookie:
56 | # Store a local instance of the user data so we don't need
57 | # a round-trip to Facebook on every request
58 | user = User.get_by_key_name(cookie["uid"])
59 | if not user:
60 | graph = facebook.GraphAPI(cookie["access_token"])
61 | profile = graph.get_object("me")
62 | user = User(key_name=str(profile["id"]),
63 | id=str(profile["id"]),
64 | name=profile["name"],
65 | profile_url=profile["link"],
66 | access_token=cookie["access_token"])
67 | user.put()
68 | elif user.access_token != cookie["access_token"]:
69 | user.access_token = cookie["access_token"]
70 | user.put()
71 | self._current_user = user
72 | return self._current_user
73 |
74 |
75 | class HomeHandler(BaseHandler):
76 | def get(self):
77 | path = os.path.join(os.path.dirname(__file__), "example.html")
78 | args = dict(current_user=self.current_user,
79 | facebook_app_id=FACEBOOK_APP_ID)
80 | self.response.out.write(template.render(path, args))
81 |
82 |
83 | def main():
84 | util.run_wsgi_app(webapp.WSGIApplication([(r"/", HomeHandler)]))
85 |
86 |
87 | if __name__ == "__main__":
88 | main()
89 |
--------------------------------------------------------------------------------
/examples/tornado/example.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Copyright 2010 Facebook
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License. You may obtain
7 | # a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 | # License for the specific language governing permissions and limitations
15 | # under the License.
16 |
17 | """A barebones Tornado application that uses Facebook for login.
18 |
19 | Assumes a database with a schema as specified in schema.sql. We store a
20 | local copy of basic user data so we don't need to make a round-trip to
21 | the Facebook API on every request once a user has logged in.
22 | """
23 |
24 | import facebook
25 | import tornado.database
26 | import tornado.httpserver
27 | import tornado.options
28 | import tornado.web
29 |
30 | from tornado.options import define, options
31 |
32 | define("port", default=8888, help="run on the given port", type=int)
33 | define("facebook_app_id", help="Facebook Application ID")
34 | define("facebook_app_secret", help="Facebook Application Secret")
35 | define("mysql_host", help="MySQL database host")
36 | define("mysql_database", help="MySQL database database")
37 | define("mysql_user", help="MySQL database user")
38 | define("mysql_password", help="MySQL database password")
39 |
40 |
41 | class BaseHandler(tornado.web.RequestHandler):
42 | """Implements authentication via the Facebook JavaScript SDK cookie."""
43 | def get_current_user(self):
44 | cookies = dict((n, self.cookies[n].value) for n in self.cookies.keys())
45 | cookie = facebook.get_user_from_cookie(
46 | cookies, options.facebook_app_id, options.facebook_app_secret)
47 | if not cookie: return None
48 | user = self.db.get(
49 | "SELECT * FROM users WHERE id = %s", cookie["uid"])
50 | if not user:
51 | # TODO: Make this fetch async rather than blocking
52 | graph = facebook.GraphAPI(cookie["access_token"])
53 | profile = graph.get_object("me")
54 | self.db.execute(
55 | "REPLACE INTO users (id, name, profile_url, access_token) "
56 | "VALUES (%s,%s,%s,%s)", profile["id"], profile["name"],
57 | profile["link"], cookie["access_token"])
58 | user = self.db.get(
59 | "SELECT * FROM users WHERE id = %s", profile["id"])
60 | elif user.access_token != cookie["access_token"]:
61 | self.db.execute(
62 | "UPDATE users SET access_token = %s WHERE id = %s",
63 | cookie["access_token"], user.id)
64 | return user
65 |
66 | @property
67 | def db(self):
68 | if not hasattr(BaseHandler, "_db"):
69 | BaseHandler._db = tornado.database.Connection(
70 | host=options.mysql_host, database=options.mysql_database,
71 | user=options.mysql_user, password=options.mysql_password)
72 | return BaseHandler._db
73 |
74 |
75 | class MainHandler(BaseHandler):
76 | def get(self):
77 | self.render("example.html", options=options)
78 |
79 |
80 | def main():
81 | tornado.options.parse_command_line()
82 | http_server = tornado.httpserver.HTTPServer(tornado.web.Application([
83 | (r"/", MainHandler),
84 | ]))
85 | http_server.listen(options.port)
86 | tornado.ioloop.IOLoop.instance().start()
87 |
88 |
89 | if __name__ == "__main__":
90 | main()
91 |
--------------------------------------------------------------------------------
/examples/newsfeed/facebookclient.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Copyright 2010 Facebook
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License. You may obtain
7 | # a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 | # License for the specific language governing permissions and limitations
15 | # under the License.
16 |
17 | """A Facebook stream client written against the Facebook Graph API."""
18 |
19 | FACEBOOK_APP_ID = "your app id"
20 | FACEBOOK_APP_SECRET = "your app secret"
21 |
22 | import datetime
23 | import facebook
24 | import os
25 | import os.path
26 | import wsgiref.handlers
27 |
28 | from google.appengine.ext import db
29 | from google.appengine.ext import webapp
30 | from google.appengine.ext.webapp import util
31 | from google.appengine.ext.webapp import template
32 |
33 |
34 | class User(db.Model):
35 | id = db.StringProperty(required=True)
36 | created = db.DateTimeProperty(auto_now_add=True)
37 | updated = db.DateTimeProperty(auto_now=True)
38 | name = db.StringProperty(required=True)
39 | profile_url = db.StringProperty(required=True)
40 | access_token = db.StringProperty(required=True)
41 |
42 |
43 | class BaseHandler(webapp.RequestHandler):
44 | """Provides access to the active Facebook user in self.current_user
45 |
46 | The property is lazy-loaded on first access, using the cookie saved
47 | by the Facebook JavaScript SDK to determine the user ID of the active
48 | user. See http://developers.facebook.com/docs/authentication/ for
49 | more information.
50 | """
51 | @property
52 | def current_user(self):
53 | """Returns the active user, or None if the user has not logged in."""
54 | if not hasattr(self, "_current_user"):
55 | self._current_user = None
56 | cookie = facebook.get_user_from_cookie(
57 | self.request.cookies, FACEBOOK_APP_ID, FACEBOOK_APP_SECRET)
58 | if cookie:
59 | # Store a local instance of the user data so we don't need
60 | # a round-trip to Facebook on every request
61 | user = User.get_by_key_name(cookie["uid"])
62 | if not user:
63 | graph = facebook.GraphAPI(cookie["access_token"])
64 | profile = graph.get_object("me")
65 | user = User(key_name=str(profile["id"]),
66 | id=str(profile["id"]),
67 | name=profile["name"],
68 | profile_url=profile["link"],
69 | access_token=cookie["access_token"])
70 | user.put()
71 | elif user.access_token != cookie["access_token"]:
72 | user.access_token = cookie["access_token"]
73 | user.put()
74 | self._current_user = user
75 | return self._current_user
76 |
77 | @property
78 | def graph(self):
79 | """Returns a Graph API client for the current user."""
80 | if not hasattr(self, "_graph"):
81 | if self.current_user:
82 | self._graph = facebook.GraphAPI(self.current_user.access_token)
83 | else:
84 | self._graph = facebook.GraphAPI()
85 | return self._graph
86 |
87 | def render(self, path, **kwargs):
88 | args = dict(current_user=self.current_user,
89 | facebook_app_id=FACEBOOK_APP_ID)
90 | args.update(kwargs)
91 | path = os.path.join(os.path.dirname(__file__), "templates", path)
92 | self.response.out.write(template.render(path, args))
93 |
94 |
95 | class HomeHandler(BaseHandler):
96 | def get(self):
97 | if not self.current_user:
98 | self.render("index.html")
99 | return
100 | try:
101 | news_feed = self.graph.get_connections("me", "home")
102 | except facebook.GraphAPIError:
103 | self.render("index.html")
104 | return
105 | except:
106 | news_feed = {"data": []}
107 | for post in news_feed["data"]:
108 | post["created_time"] = datetime.datetime.strptime(
109 | post["created_time"], "%Y-%m-%dT%H:%M:%S+0000") + \
110 | datetime.timedelta(hours=7)
111 | self.render("home.html", news_feed=news_feed)
112 |
113 |
114 | class PostHandler(BaseHandler):
115 | def post(self):
116 | message = self.request.get("message")
117 | if not self.current_user or not message:
118 | self.redirect("/")
119 | return
120 | try:
121 | self.graph.put_wall_post(message)
122 | except:
123 | pass
124 | self.redirect("/")
125 |
126 |
127 | def main():
128 | debug = os.environ.get("SERVER_SOFTWARE", "").startswith("Development/")
129 | util.run_wsgi_app(webapp.WSGIApplication([
130 | (r"/", HomeHandler),
131 | (r"/post", PostHandler),
132 | ], debug=debug))
133 |
134 |
135 | if __name__ == "__main__":
136 | main()
137 |
--------------------------------------------------------------------------------
/examples/oauth/facebookoauth.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Copyright 2010 Facebook
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License. You may obtain
7 | # a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 | # License for the specific language governing permissions and limitations
15 | # under the License.
16 |
17 | """A barebones AppEngine application that uses Facebook for login.
18 |
19 | This application uses OAuth 2.0 directly rather than relying on Facebook's
20 | JavaScript SDK for login. It also accesses the Facebook Graph API directly
21 | rather than using the Python SDK. It is designed to illustrate how easy
22 | it is to use the Facebook Platform without any third party code.
23 |
24 | See the "appengine" directory for an example using the JavaScript SDK.
25 | Using JavaScript is recommended if it is feasible for your application,
26 | as it handles some complex authentication states that can only be detected
27 | in client-side code.
28 | """
29 |
30 | FACEBOOK_APP_ID = "your app id"
31 | FACEBOOK_APP_SECRET = "your app secret"
32 |
33 | import base64
34 | import cgi
35 | import Cookie
36 | import email.utils
37 | import hashlib
38 | import hmac
39 | import logging
40 | import os.path
41 | import time
42 | import urllib
43 | import wsgiref.handlers
44 |
45 | from django.utils import simplejson as json
46 | from google.appengine.ext import db
47 | from google.appengine.ext import webapp
48 | from google.appengine.ext.webapp import util
49 | from google.appengine.ext.webapp import template
50 |
51 |
52 | class User(db.Model):
53 | id = db.StringProperty(required=True)
54 | created = db.DateTimeProperty(auto_now_add=True)
55 | updated = db.DateTimeProperty(auto_now=True)
56 | name = db.StringProperty(required=True)
57 | profile_url = db.StringProperty(required=True)
58 | access_token = db.StringProperty(required=True)
59 |
60 |
61 | class BaseHandler(webapp.RequestHandler):
62 | @property
63 | def current_user(self):
64 | """Returns the logged in Facebook user, or None if unconnected."""
65 | if not hasattr(self, "_current_user"):
66 | self._current_user = None
67 | user_id = parse_cookie(self.request.cookies.get("fb_user"))
68 | if user_id:
69 | self._current_user = User.get_by_key_name(user_id)
70 | return self._current_user
71 |
72 |
73 | class HomeHandler(BaseHandler):
74 | def get(self):
75 | path = os.path.join(os.path.dirname(__file__), "oauth.html")
76 | args = dict(current_user=self.current_user)
77 | self.response.out.write(template.render(path, args))
78 |
79 |
80 | class LoginHandler(BaseHandler):
81 | def get(self):
82 | verification_code = self.request.get("code")
83 | args = dict(client_id=FACEBOOK_APP_ID, redirect_uri=self.request.path_url)
84 | if self.request.get("code"):
85 | args["client_secret"] = FACEBOOK_APP_SECRET
86 | args["code"] = self.request.get("code")
87 | response = cgi.parse_qs(urllib.urlopen(
88 | "https://graph.facebook.com/oauth/access_token?" +
89 | urllib.urlencode(args)).read())
90 | access_token = response["access_token"][-1]
91 |
92 | # Download the user profile and cache a local instance of the
93 | # basic profile info
94 | profile = json.load(urllib.urlopen(
95 | "https://graph.facebook.com/me?" +
96 | urllib.urlencode(dict(access_token=access_token))))
97 | user = User(key_name=str(profile["id"]), id=str(profile["id"]),
98 | name=profile["name"], access_token=access_token,
99 | profile_url=profile["link"])
100 | user.put()
101 | set_cookie(self.response, "fb_user", str(profile["id"]),
102 | expires=time.time() + 30 * 86400)
103 | self.redirect("/")
104 | else:
105 | self.redirect(
106 | "https://graph.facebook.com/oauth/authorize?" +
107 | urllib.urlencode(args))
108 |
109 |
110 | class LogoutHandler(BaseHandler):
111 | def get(self):
112 | set_cookie(self.response, "fb_user", "", expires=time.time() - 86400)
113 | self.redirect("/")
114 |
115 |
116 | def set_cookie(response, name, value, domain=None, path="/", expires=None):
117 | """Generates and signs a cookie for the give name/value"""
118 | timestamp = str(int(time.time()))
119 | value = base64.b64encode(value)
120 | signature = cookie_signature(value, timestamp)
121 | cookie = Cookie.BaseCookie()
122 | cookie[name] = "|".join([value, timestamp, signature])
123 | cookie[name]["path"] = path
124 | if domain: cookie[name]["domain"] = domain
125 | if expires:
126 | cookie[name]["expires"] = email.utils.formatdate(
127 | expires, localtime=False, usegmt=True)
128 | response.headers._headers.append(("Set-Cookie", cookie.output()[12:]))
129 |
130 |
131 | def parse_cookie(value):
132 | """Parses and verifies a cookie value from set_cookie"""
133 | if not value: return None
134 | parts = value.split("|")
135 | if len(parts) != 3: return None
136 | if cookie_signature(parts[0], parts[1]) != parts[2]:
137 | logging.warning("Invalid cookie signature %r", value)
138 | return None
139 | timestamp = int(parts[1])
140 | if timestamp < time.time() - 30 * 86400:
141 | logging.warning("Expired cookie %r", value)
142 | return None
143 | try:
144 | return base64.b64decode(parts[0]).strip()
145 | except:
146 | return None
147 |
148 |
149 | def cookie_signature(*parts):
150 | """Generates a cookie signature.
151 |
152 | We use the Facebook app secret since it is different for every app (so
153 | people using this example don't accidentally all use the same secret).
154 | """
155 | hash = hmac.new(FACEBOOK_APP_SECRET, digestmod=hashlib.sha1)
156 | for part in parts: hash.update(part)
157 | return hash.hexdigest()
158 |
159 |
160 | def main():
161 | util.run_wsgi_app(webapp.WSGIApplication([
162 | (r"/", HomeHandler),
163 | (r"/auth/login", LoginHandler),
164 | (r"/auth/logout", LogoutHandler),
165 | ]))
166 |
167 |
168 | if __name__ == "__main__":
169 | main()
170 |
--------------------------------------------------------------------------------
/src/facebook.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Copyright 2010 Facebook
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License. You may obtain
7 | # a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 | # License for the specific language governing permissions and limitations
15 | # under the License.
16 |
17 | """Python client library for the Facebook Platform.
18 |
19 | This client library is designed to support the Graph API and the official
20 | Facebook JavaScript SDK, which is the canonical way to implement
21 | Facebook authentication. Read more about the Graph API at
22 | http://developers.facebook.com/docs/api. You can download the Facebook
23 | JavaScript SDK at http://github.com/facebook/connect-js/.
24 |
25 | If your application is using Google AppEngine's webapp framework, your
26 | usage of this module might look like this:
27 |
28 | user = facebook.get_user_from_cookie(self.request.cookies, key, secret)
29 | if user:
30 | graph = facebook.GraphAPI(user["access_token"])
31 | profile = graph.get_object("me")
32 | friends = graph.get_connections("me", "friends")
33 |
34 | """
35 |
36 | import cgi
37 | import hashlib
38 | import time
39 | import urllib
40 |
41 | # Find a JSON parser
42 | try:
43 | import json
44 | _parse_json = lambda s: json.loads(s)
45 | except ImportError:
46 | try:
47 | import simplejson
48 | _parse_json = lambda s: simplejson.loads(s)
49 | except ImportError:
50 | # For Google AppEngine
51 | from django.utils import simplejson
52 | _parse_json = lambda s: simplejson.loads(s)
53 |
54 |
55 | class GraphAPI(object):
56 | """A client for the Facebook Graph API.
57 |
58 | See http://developers.facebook.com/docs/api for complete documentation
59 | for the API.
60 |
61 | The Graph API is made up of the objects in Facebook (e.g., people, pages,
62 | events, photos) and the connections between them (e.g., friends,
63 | photo tags, and event RSVPs). This client provides access to those
64 | primitive types in a generic way. For example, given an OAuth access
65 | token, this will fetch the profile of the active user and the list
66 | of the user's friends:
67 |
68 | graph = facebook.GraphAPI(access_token)
69 | user = graph.get_object("me")
70 | friends = graph.get_connections(user["id"], "friends")
71 |
72 | You can see a list of all of the objects and connections supported
73 | by the API at http://developers.facebook.com/docs/reference/api/.
74 |
75 | You can obtain an access token via OAuth or by using the Facebook
76 | JavaScript SDK. See http://developers.facebook.com/docs/authentication/
77 | for details.
78 |
79 | If you are using the JavaScript SDK, you can use the
80 | get_user_from_cookie() method below to get the OAuth access token
81 | for the active user from the cookie saved by the SDK.
82 |
83 | A locale parameter can be passed that overrides the default
84 | locale.
85 | """
86 | def __init__(self, access_token=None, locale=None):
87 | self.access_token = access_token
88 | self.locale = locale
89 |
90 | def get_object(self, id, **args):
91 | """Fetchs the given object from the graph."""
92 | return self.request(id, args)
93 |
94 | def get_objects(self, ids, **args):
95 | """Fetchs all of the given object from the graph.
96 |
97 | We return a map from ID to object. If any of the IDs are invalid,
98 | we raise an exception.
99 | """
100 | args["ids"] = ",".join(ids)
101 | return self.request("", args)
102 |
103 | def get_connections(self, id, connection_name, **args):
104 | """Fetchs the connections for given object."""
105 | return self.request(id + "/" + connection_name, args)
106 |
107 | def put_object(self, parent_object, connection_name, **data):
108 | """Writes the given object to the graph, connected to the given parent.
109 |
110 | For example,
111 |
112 | graph.put_object("me", "feed", message="Hello, world")
113 |
114 | writes "Hello, world" to the active user's wall. Likewise, this
115 | will comment on a the first post of the active user's feed:
116 |
117 | feed = graph.get_connections("me", "feed")
118 | post = feed["data"][0]
119 | graph.put_object(post["id"], "comments", message="First!")
120 |
121 | See http://developers.facebook.com/docs/api#publishing for all of
122 | the supported writeable objects.
123 |
124 | Most write operations require extended permissions. For example,
125 | publishing wall posts requires the "publish_stream" permission. See
126 | http://developers.facebook.com/docs/authentication/ for details about
127 | extended permissions.
128 | """
129 | assert self.access_token, "Write operations require an access token"
130 | return self.request(parent_object + "/" + connection_name, post_args=data)
131 |
132 | def put_wall_post(self, message, attachment={}, profile_id="me"):
133 | """Writes a wall post to the given profile's wall.
134 |
135 | We default to writing to the authenticated user's wall if no
136 | profile_id is specified.
137 |
138 | attachment adds a structured attachment to the status message being
139 | posted to the Wall. It should be a dictionary of the form:
140 |
141 | {"name": "Link name"
142 | "link": "http://www.example.com/",
143 | "caption": "{*actor*} posted a new review",
144 | "description": "This is a longer description of the attachment",
145 | "picture": "http://www.example.com/thumbnail.jpg"}
146 |
147 | """
148 | return self.put_object(profile_id, "feed", message=message, **attachment)
149 |
150 | def put_comment(self, object_id, message):
151 | """Writes the given comment on the given post."""
152 | return self.put_object(object_id, "comments", message=message)
153 |
154 | def put_like(self, object_id):
155 | """Likes the given post."""
156 | return self.put_object(object_id, "likes")
157 |
158 | def delete_object(self, id):
159 | """Deletes the object with the given ID from the graph."""
160 | self.request(id, post_args={"method": "delete"})
161 |
162 | def request(self, path, args=None, post_args=None):
163 | """Fetches the given path in the Graph API.
164 |
165 | We translate args to a valid query string. If post_args is given,
166 | we send a POST request to the given path with the given arguments.
167 | """
168 | if not args: args = {}
169 | if self.access_token:
170 | if post_args is not None:
171 | post_args["access_token"] = self.access_token
172 | else:
173 | args["access_token"] = self.access_token
174 | if self.locale is not None:
175 | args["locale"] = self.locale
176 | post_data = None if post_args is None else urllib.urlencode(post_args)
177 | file = urllib.urlopen("https://graph.facebook.com/" + path + "?" +
178 | urllib.urlencode(args), post_data)
179 | try:
180 | response = _parse_json(file.read())
181 | finally:
182 | file.close()
183 | if response.get("error"):
184 | raise GraphAPIError(response["error"]["type"],
185 | response["error"]["message"])
186 | return response
187 |
188 |
189 | class GraphAPIError(Exception):
190 | def __init__(self, type, message):
191 | Exception.__init__(self, message)
192 | self.type = type
193 |
194 |
195 | def get_user_from_cookie(cookies, app_id, app_secret):
196 | """Parses the cookie set by the official Facebook JavaScript SDK.
197 |
198 | cookies should be a dictionary-like object mapping cookie names to
199 | cookie values.
200 |
201 | If the user is logged in via Facebook, we return a dictionary with the
202 | keys "uid" and "access_token". The former is the user's Facebook ID,
203 | and the latter can be used to make authenticated requests to the Graph API.
204 | If the user is not logged in, we return None.
205 |
206 | Download the official Facebook JavaScript SDK at
207 | http://github.com/facebook/connect-js/. Read more about Facebook
208 | authentication at http://developers.facebook.com/docs/authentication/.
209 | """
210 | cookie = cookies.get("fbs_" + app_id, "")
211 | if not cookie: return None
212 | args = dict((k, v[-1]) for k, v in cgi.parse_qs(cookie.strip('"')).items())
213 | payload = "".join(k + "=" + args[k] for k in sorted(args.keys())
214 | if k != "sig")
215 | sig = hashlib.md5(payload + app_secret).hexdigest()
216 | expires = int(args["expires"])
217 | if sig == args.get("sig") and (expires == 0 or time.time() < expires):
218 | return args
219 | else:
220 | return None
221 |
--------------------------------------------------------------------------------