├── test ├── __init__.py └── test_facebook.py ├── examples ├── newsfeed │ ├── static │ │ ├── robots.txt │ │ ├── favicon.ico │ │ └── base.css │ ├── app.yaml │ ├── templates │ │ ├── index.html │ │ ├── base.html │ │ └── home.html │ └── facebookclient.py ├── flask │ ├── run.py │ ├── app │ │ ├── __init__.py │ │ ├── templates │ │ │ ├── login.html │ │ │ ├── index.html │ │ │ └── base.html │ │ ├── static │ │ │ └── css │ │ │ │ └── style.css │ │ ├── models.py │ │ └── views.py │ ├── requirements.txt │ └── config.py ├── oauth │ ├── app.yaml │ ├── oauth.html │ └── facebookoauth.py ├── appengine │ ├── app.yaml │ ├── example.html │ └── example.py ├── tornado │ ├── schema.sql │ ├── example.html │ └── example.py └── get_posts.py ├── MANIFEST.in ├── CONTRIBUTING.rst ├── .travis.yml ├── .gitignore ├── docs ├── install.rst ├── index.rst ├── support.rst ├── make.bat ├── Makefile ├── api.rst └── conf.py ├── facebook ├── version.py └── __init__.py ├── setup.py └── README.rst /test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/newsfeed/static/robots.txt: -------------------------------------------------------------------------------- 1 | User-Agent: * 2 | Disallow: / 3 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | recursive-include examples *.css *.html *.ico *.py *.sql *.txt *.yaml 3 | -------------------------------------------------------------------------------- /examples/newsfeed/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertelab/facebook-sdk/master/examples/newsfeed/static/favicon.ico -------------------------------------------------------------------------------- /examples/flask/run.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from os import environ 3 | 4 | from app import app, db 5 | 6 | db.create_all() 7 | app.run(host='0.0.0.0', port=8000) 8 | -------------------------------------------------------------------------------- /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/flask/app/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask.ext.sqlalchemy import SQLAlchemy 3 | 4 | app = Flask(__name__) 5 | app.config.from_object('config') 6 | db = SQLAlchemy(app) 7 | 8 | from app import views, models 9 | -------------------------------------------------------------------------------- /examples/appengine/app.yaml: -------------------------------------------------------------------------------- 1 | application: facebook-example-py27 2 | version: 1 3 | runtime: python27 4 | api_version: 1 5 | threadsafe: true 6 | 7 | handlers: 8 | - url: /.* 9 | script: example.app 10 | 11 | libraries: 12 | - name: jinja2 13 | version: latest 14 | -------------------------------------------------------------------------------- /examples/flask/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==0.10.1 2 | Flask-SQLAlchemy==1.0 3 | Jinja2==2.7.2 4 | MarkupSafe==0.19 5 | SQLAlchemy==0.9.4 6 | Werkzeug==0.9.4 7 | argparse==1.2.1 8 | distribute==0.6.24 9 | facebook-sdk==0.4.0 10 | itsdangerous==0.24 11 | requests==2.2.1 12 | wsgiref==0.1.2 13 | -------------------------------------------------------------------------------- /examples/flask/app/templates/login.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 |
4 |

Python for Facebook SDK

5 |

Flask example

6 |
7 | Sign in 8 |
9 |
10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /examples/flask/config.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | 3 | # App details 4 | BASE_DIRECTORY = path.abspath(path.dirname(__file__)) 5 | DEBUG = True 6 | SECRET_KEY = 'keep_it_like_a_secret' 7 | 8 | # Database details 9 | SQLALCHEMY_DATABASE_URI = '{0}{1}'.format('sqlite:///', 10 | path.join(BASE_DIRECTORY, 'app.db')) 11 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | ======================================= 2 | Contributing to the Facebook Python SDK 3 | ======================================= 4 | 5 | See `the "Contributing" section of the package's documentation`_ for details on 6 | submitting pull requests, code style, and more. 7 | 8 | .. _the "Contributing" section of the package's documentation: https://facebook-sdk.readthedocs.org/en/latest/support.html#contributing 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.3" 4 | - "2.7" 5 | - "2.6" 6 | install: 7 | - "pip install flake8" 8 | - "pip install ." 9 | before_script: 10 | - "flake8 --ignore=F401 examples" 11 | - "flake8 facebook" 12 | - "flake8 test" 13 | script: python -m test.test_facebook 14 | env: 15 | global: 16 | - FACEBOOK_APP_ID=198798870326423 17 | - FACEBOOK_SECRET=2db4d76fe8a336cf292470c20a5a5684 18 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /examples/flask/app/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 |
4 |

7 |

8 |

Hello, {{ user['name'] }}.

9 | Log out 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | __pycache__ 21 | 22 | # Installer logs 23 | pip-log.txt 24 | 25 | # Unit test / coverage reports 26 | .coverage 27 | .tox 28 | nosetests.xml 29 | 30 | # Translations 31 | *.mo 32 | 33 | # Mr Developer 34 | .mr.developer.cfg 35 | .project 36 | .pydevproject 37 | -------------------------------------------------------------------------------- /examples/flask/app/static/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 3 | text-align: center; 4 | } 5 | 6 | .center { 7 | margin: auto; 8 | position: absolute; 9 | top: 0; left: 0; bottom: 0; right: 0; 10 | width: 50%; 11 | height: 50%; 12 | min-width: 200px; 13 | max-width: 400px; 14 | padding: 40px; 15 | } 16 | 17 | .circle-image { 18 | width: 200px; 19 | height: 200px; 20 | border-radius: 100%; 21 | display: block; 22 | } 23 | -------------------------------------------------------------------------------- /docs/install.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Installation 3 | ============ 4 | 5 | The SDK currently supports Python 2.6, 2.7, and 3.3. The `requests`_ package is 6 | required. 7 | 8 | We recommend using `pip`_ and `virtualenv`_ to install the SDK. Please note 9 | that the SDK's Python package is called **facebook-sdk**: :: 10 | 11 | virtualenv facebookenv 12 | source facebookenv/bin/activate 13 | pip install facebook-sdk 14 | 15 | .. _requests: https://pypi.python.org/pypi/requests 16 | .. _pip: http://www.pip-installer.org/ 17 | .. _virtualenv: http://www.virtualenv.org/ 18 | -------------------------------------------------------------------------------- /examples/flask/app/models.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from app import db 4 | 5 | 6 | class User(db.Model): 7 | __tablename__ = 'users' 8 | 9 | id = db.Column(db.String, nullable=False, primary_key=True) 10 | created = db.Column(db.DateTime, default=datetime.utcnow, nullable=False) 11 | updated = db.Column(db.DateTime, default=datetime.utcnow, nullable=False, 12 | onupdate=datetime.utcnow) 13 | name = db.Column(db.String, nullable=False) 14 | profile_url = db.Column(db.String, nullable=False) 15 | access_token = db.Column(db.String, nullable=False) 16 | -------------------------------------------------------------------------------- /facebook/version.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2014 Martey Dodoo 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 | __version__ = "1.0.0-alpha" 18 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | ======================= 2 | Facebook SDK for Python 3 | ======================= 4 | 5 | .. toctree:: 6 | :maxdepth: 2 7 | 8 | install 9 | support 10 | api 11 | 12 | This client library is designed to support the `Facebook Graph API`_ and the 13 | official `Facebook JavaScript SDK`_, which is the canonical way to implement 14 | Facebook authentication. You can read more about the Graph API by accessing its 15 | `official documentation`_. 16 | 17 | .. _Facebook Graph API: https://developers.facebook.com/docs/reference/api/ 18 | .. _Facebook JavaScript SDK: https://developers.facebook.com/docs/reference/javascript/ 19 | .. _official documentation: https://developers.facebook.com/docs/reference/api/ 20 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from setuptools import setup 3 | 4 | exec(open("facebook/version.py").read()) 5 | 6 | setup( 7 | name='facebook-sdk', 8 | version=__version__, 9 | description='This client library is designed to support the Facebook ' 10 | 'Graph API and the official Facebook JavaScript SDK, which ' 11 | 'is the canonical way to implement Facebook authentication.', 12 | author='Facebook', 13 | maintainer='Martey Dodoo', 14 | maintainer_email='facebook-sdk@marteydodoo.com', 15 | url='https://github.com/pythonforfacebook/facebook-sdk', 16 | license='Apache', 17 | packages=["facebook"], 18 | long_description=open("README.rst").read(), 19 | classifiers=[ 20 | 'License :: OSI Approved :: Apache Software License', 21 | 'Programming Language :: Python :: 2.6', 22 | 'Programming Language :: Python :: 2.7', 23 | 'Programming Language :: Python :: 3.3', 24 | ], 25 | install_requires=[ 26 | 'requests', 27 | ], 28 | ) 29 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/get_posts.py: -------------------------------------------------------------------------------- 1 | """ 2 | A simple example script to get all posts on a user's timeline. 3 | Originally created by Mitchell Stewart. 4 | 5 | """ 6 | import facebook 7 | import requests 8 | 9 | 10 | def some_action(post): 11 | """ Here you might want to do something with each post. E.g. grab the 12 | post's message (post['message']) or the post's picture (post['picture']). 13 | In this implementation we just print the post's created time. 14 | """ 15 | print(post['created_time']) 16 | 17 | 18 | # You'll need an access token here to do anything. You can get a temporary one 19 | # here: https://developers.facebook.com/tools/explorer/ 20 | access_token = '' 21 | # Look at Bill Gates's profile for this example by using his Facebook id. 22 | user = 'BillGates' 23 | 24 | graph = facebook.GraphAPI(access_token) 25 | profile = graph.get_object(user) 26 | posts = graph.get_connections(profile['id'], 'posts') 27 | 28 | # Wrap this block in a while loop so we can keep paginating requests until 29 | # finished. 30 | while True: 31 | try: 32 | # Perform some action on each post in the collection we receive from 33 | # Facebook. 34 | [some_action(post=post) for post in posts['data']] 35 | # Attempt to make a request to the next page of data, if it exists. 36 | posts = requests.get(posts['paging']['next']).json() 37 | except KeyError: 38 | # When there are no more pages (['paging']['next']), break from the 39 | # loop and end the script. 40 | break 41 | -------------------------------------------------------------------------------- /examples/newsfeed/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {% block title %}Facebook Client Example{% endblock %} 6 | 7 | {% block head %}{% endblock %} 8 | 9 | 10 |
This application is a demo of the Facebook Graph API, the core part of the Facebook Platform. See source code »
11 |
{% block body %}{% endblock %}
12 |
13 | 28 | 29 | 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 | 17 | {% if current_user %} 18 |
19 | Upload photo test: 20 |
21 | Enter URL to URLFetch from: 22 | 23 |
24 |
25 | {% endif %} 26 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /docs/support.rst: -------------------------------------------------------------------------------- 1 | ===================== 2 | Support & Development 3 | ===================== 4 | 5 | Mailing List 6 | ============ 7 | 8 | Questions about the SDK should be sent to its `Google Group`_. 9 | 10 | .. _Google Group: https://groups.google.com/group/pythonforfacebook 11 | 12 | Reporting Bugs 13 | ============== 14 | 15 | Bugs with the SDK should be reported on the `issue tracker at Github`_. Bugs 16 | with Facebook's Graph API should be reported on `Facebook's bugtracker`_. 17 | 18 | .. _issue tracker at Github: https://github.com/pythonforfacebook/facebook-sdk/issues 19 | .. _Facebook's bugtracker: https://developers.facebook.com/x/bugs/ 20 | 21 | Security Issues 22 | --------------- 23 | 24 | Security issues with the SDK that would adversely affect users if reported 25 | publicly should be sent through private email to the project maintainer at 26 | martey @ marteydodoo.com (GPG key ID is 0x2cd700988f74c455). 27 | 28 | Contributing 29 | ============ 30 | 31 | Use Github Pull Requests 32 | ------------------------ 33 | 34 | All potential code changes should be submitted as pull requests on Github. A 35 | pull request should only include commits directly applicable to its change 36 | (e.g. a pull request that adds a new feature should not include PEP8 changes in 37 | an unrelated area of the code). 38 | 39 | Code Style 40 | ---------- 41 | 42 | Code *must* be compliant with `PEP 8`_. Use the latest version of `pep8`_ or 43 | `flake8`_ to catch issues. 44 | 45 | Git commit messages should include `a summary and proper line wrapping`_. 46 | 47 | .. _PEP 8: http://www.python.org/dev/peps/pep-0008/ 48 | .. _pep8: https://pypi.python.org/pypi/pep8 49 | .. _flake8: https://pypi.python.org/pypi/flake8 50 | .. _a summary and proper line wrapping: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html 51 | 52 | Update Tests and Documentation 53 | ------------------------------ 54 | 55 | All non-trivial changes should include full test coverage. Please review 56 | the package's documentation to ensure that it is up to date with any changes. 57 | 58 | -------------------------------------------------------------------------------- /examples/flask/app/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | {{ app_name }} 14 | 15 | 16 | 17 | 18 | 25 | 26 | 27 | 50 | 51 |
52 | {% block content %} {% endblock %} 53 |
54 | 55 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /examples/newsfeed/templates/home.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block body %} 4 | 8 |
9 | 10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | 20 | {% for post in news_feed.data %} 21 |
22 |
23 |
24 |
25 | {{ post.from.name|escape }} 26 | {% if post.message %}{{ post.message|escape }}{% endif %} 27 |
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 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | =================== 2 | Facebook Python SDK 3 | =================== 4 | 5 | This client library is designed to support the `Facebook Graph API`_ and the 6 | official `Facebook JavaScript SDK`_, which is the canonical way to implement 7 | Facebook authentication. You can read more about the Graph API by accessing its 8 | `official documentation`_. 9 | 10 | .. _Facebook Graph API: https://developers.facebook.com/docs/reference/api/ 11 | .. _Facebook JavaScript SDK: https://developers.facebook.com/docs/reference/javascript/ 12 | .. _official documentation: https://developers.facebook.com/docs/reference/api/ 13 | 14 | Examples 15 | ======== 16 | 17 | Basic usage: 18 | 19 | .. code-block:: python 20 | 21 | import facebook 22 | graph = facebook.GraphAPI(oauth_access_token) 23 | profile = graph.get_object("me") 24 | friends = graph.get_connections("me", "friends") 25 | graph.put_object("me", "feed", message="I am writing on my wall!") 26 | 27 | Photo uploads: 28 | 29 | .. code-block:: python 30 | 31 | graph = facebook.GraphAPI(oauth_access_token) 32 | tags = json.dumps([{'x':50, 'y':50, 'tag_uid':12345}, {'x':10, 'y':60, 'tag_text':'a turtle'}]) 33 | graph.put_photo(open('img.jpg'), 'Look at this cool photo!', album_id_or_None, tags=tags) 34 | 35 | If you are using the module within a web application with the JavaScript SDK, 36 | you can also use the module to use Facebook for login, parsing the cookie set 37 | by the JavaScript SDK for logged in users. For example, in Google AppEngine, 38 | you could get the profile of the logged in user with: 39 | 40 | .. code-block:: python 41 | 42 | user = facebook.get_user_from_cookie(self.request.cookies, key, secret) 43 | if user: 44 | graph = facebook.GraphAPI(user["access_token"]) 45 | profile = graph.get_object("me") 46 | friends = graph.get_connections("me", "friends") 47 | 48 | 49 | You can see a full AppEngine example application in examples/appengine. 50 | 51 | Licensing 52 | ========= 53 | 54 | This library uses the `Apache License, version 2.0`_. Please see the library's 55 | individual files for more information. 56 | 57 | .. _Apache License, version 2.0: http://www.apache.org/licenses/LICENSE-2.0.html 58 | 59 | Reporting Issues 60 | ================ 61 | 62 | If you have bugs or other issues specifically pertaining to this library, file 63 | them `here`_. Bugs with the Graph API should be filed on `Facebook's 64 | bugtracker`_. 65 | 66 | .. _here: https://github.com/pythonforfacebook/facebook-sdk/issues 67 | .. _Facebook's bugtracker: https://developers.facebook.com/bugs/ 68 | 69 | 70 | Support & Discussion 71 | ==================== 72 | 73 | Documentation is available at https://facebook-sdk.readthedocs.org/en/latest/. 74 | 75 | Have a question? Need help? Visit the library's `Google Group`_. 76 | 77 | .. _Google Group: https://groups.google.com/group/pythonforfacebook 78 | -------------------------------------------------------------------------------- /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/flask/app/views.py: -------------------------------------------------------------------------------- 1 | from facebook import get_user_from_cookie, GraphAPI 2 | from flask import g, render_template, redirect, request, session, url_for 3 | 4 | from app import app, db 5 | from models import User 6 | 7 | # Facebook app details 8 | FB_APP_ID = '' 9 | FB_APP_NAME = '' 10 | FB_APP_SECRET = '' 11 | 12 | 13 | @app.route('/') 14 | def index(): 15 | # If a user was set in the get_current_user function before the request, 16 | # the user is logged in. 17 | if g.user: 18 | return render_template('index.html', app_id=FB_APP_ID, 19 | app_name=FB_APP_NAME, user=g.user) 20 | # Otherwise, a user is not logged in. 21 | return render_template('login.html', app_id=FB_APP_ID, name=FB_APP_NAME) 22 | 23 | 24 | @app.route('/logout') 25 | def logout(): 26 | """Log out the user from the application. 27 | 28 | Log out the user from the application by removing them from the 29 | session. Note: this does not log the user out of Facebook - this is done 30 | by the JavaScript SDK. 31 | """ 32 | session.pop('user', None) 33 | return redirect(url_for('index')) 34 | 35 | 36 | @app.before_request 37 | def get_current_user(): 38 | """Set g.user to the currently logged in user. 39 | 40 | Called before each request, get_current_user sets the global g.user 41 | variable to the currently logged in user. A currently logged in user is 42 | determined by seeing if it exists in Flask's session dictionary. 43 | 44 | If it is the first time the user is logging into this application it will 45 | create the user and insert it into the database. If the user is not logged 46 | in, None will be set to g.user. 47 | """ 48 | 49 | # Set the user in the session dictionary as a global g.user and bail out 50 | # of this function early. 51 | if session.get('user'): 52 | g.user = session.get('user') 53 | return 54 | 55 | # Attempt to get the short term access token for the current user. 56 | result = get_user_from_cookie(cookies=request.cookies, app_id=FB_APP_ID, 57 | app_secret=FB_APP_SECRET) 58 | 59 | # If there is no result, we assume the user is not logged in. 60 | if result: 61 | # Check to see if this user is already in our database. 62 | user = User.query.filter(User.id == result['uid']).first() 63 | 64 | if not user: 65 | # Not an existing user so get info 66 | graph = GraphAPI(result['access_token']) 67 | profile = graph.get_object('me') 68 | 69 | # Create the user and insert it into the database 70 | user = User(id=str(profile['id']), name=profile['name'], 71 | profile_url=profile['link'], 72 | access_token=result['access_token']) 73 | db.session.add(user) 74 | elif user.access_token != result['access_token']: 75 | # If an existing user, update the access token 76 | user.access_token = result['access_token'] 77 | 78 | # Add the user to the current session 79 | session['user'] = dict(name=user.name, profile_url=user.profile_url, 80 | id=user.id, access_token=user.access_token) 81 | 82 | # Commit changes to the database and set the user as a global g.user 83 | db.session.commit() 84 | g.user = session.get('user', None) 85 | -------------------------------------------------------------------------------- /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: 48 | return None 49 | user = self.db.get( 50 | "SELECT * FROM users WHERE id = %s", cookie["uid"]) 51 | if not user: 52 | # TODO: Make this fetch async rather than blocking 53 | graph = facebook.GraphAPI(cookie["access_token"]) 54 | profile = graph.get_object("me") 55 | self.db.execute( 56 | "REPLACE INTO users (id, name, profile_url, access_token) " 57 | "VALUES (%s,%s,%s,%s)", profile["id"], profile["name"], 58 | profile["link"], cookie["access_token"]) 59 | user = self.db.get( 60 | "SELECT * FROM users WHERE id = %s", profile["id"]) 61 | elif user.access_token != cookie["access_token"]: 62 | self.db.execute( 63 | "UPDATE users SET access_token = %s WHERE id = %s", 64 | cookie["access_token"], user.id) 65 | return user 66 | 67 | @property 68 | def db(self): 69 | if not hasattr(BaseHandler, "_db"): 70 | BaseHandler._db = tornado.database.Connection( 71 | host=options.mysql_host, database=options.mysql_database, 72 | user=options.mysql_user, password=options.mysql_password) 73 | return BaseHandler._db 74 | 75 | 76 | class MainHandler(BaseHandler): 77 | def get(self): 78 | self.render("example.html", options=options) 79 | 80 | 81 | def main(): 82 | tornado.options.parse_command_line() 83 | http_server = tornado.httpserver.HTTPServer(tornado.web.Application([ 84 | (r"/", MainHandler), 85 | ])) 86 | http_server.listen(options.port) 87 | tornado.ioloop.IOLoop.instance().start() 88 | 89 | 90 | if __name__ == "__main__": 91 | main() 92 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/test_facebook.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2013-2014 Martey Dodoo 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 | import facebook 17 | import os 18 | import unittest 19 | 20 | try: 21 | from urllib.parse import parse_qs, urlencode, urlparse 22 | except ImportError: 23 | from urlparse import parse_qs, urlparse 24 | from urllib import urlencode 25 | 26 | 27 | class FacebookTestCase(unittest.TestCase): 28 | """Sets up application ID and secret from environment.""" 29 | def setUp(self): 30 | try: 31 | self.app_id = os.environ["FACEBOOK_APP_ID"] 32 | self.secret = os.environ["FACEBOOK_SECRET"] 33 | except KeyError: 34 | raise Exception("FACEBOOK_APP_ID and FACEBOOK_SECRET " 35 | "must be set as environmental variables.") 36 | 37 | 38 | class TestGetAppAccessToken(FacebookTestCase): 39 | """ 40 | Test if application access token is returned properly. 41 | 42 | Note that this only tests if the returned token is a string, not 43 | whether it is valid. 44 | 45 | """ 46 | def test_get_app_access_token(self): 47 | token = facebook.get_app_access_token(self.app_id, self.secret) 48 | # Since "unicode" does not exist in Python 3, we cannot check 49 | # the following line with flake8 (hence the noqa comment). 50 | assert(isinstance(token, str) or isinstance(token, unicode)) # noqa 51 | 52 | 53 | class TestAPIVersion(FacebookTestCase): 54 | """Test if using the correct version of Graph API.""" 55 | def test_no_version(self): 56 | graph = facebook.GraphAPI() 57 | self.assertNotEqual(graph.version, None, "Version should not be None.") 58 | self.assertNotEqual( 59 | graph.version, "", "Version should not be an empty string.") 60 | 61 | def test_version_1_0(self): 62 | graph = facebook.GraphAPI(version=1.0) 63 | self.assertEqual(graph.get_version(), 1.0) 64 | 65 | def test_version_2_0(self): 66 | graph = facebook.GraphAPI(version=2.0) 67 | self.assertEqual(graph.get_version(), 2.0) 68 | 69 | def test_version_2_1(self): 70 | graph = facebook.GraphAPI(version=2.1) 71 | self.assertEqual(graph.get_version(), 2.1) 72 | 73 | def test_version_2_2(self): 74 | graph = facebook.GraphAPI(version=2.2) 75 | self.assertEqual(graph.get_version(), 2.2) 76 | 77 | def test_invalid_version(self): 78 | self.assertRaises(facebook.GraphAPIError, 79 | facebook.GraphAPI, version=1.2) 80 | 81 | def test_invalid_format(self): 82 | self.assertRaises(facebook.GraphAPIError, 83 | facebook.GraphAPI, version="1.a") 84 | self.assertRaises(facebook.GraphAPIError, 85 | facebook.GraphAPI, version="a.1") 86 | self.assertRaises(facebook.GraphAPIError, 87 | facebook.GraphAPI, version=1.23) 88 | self.assertRaises(facebook.GraphAPIError, 89 | facebook.GraphAPI, version="1.23") 90 | 91 | 92 | class TestFQL(FacebookTestCase): 93 | def test_fql(self): 94 | graph = facebook.GraphAPI(access_token=facebook.get_app_access_token( 95 | self.app_id, self.secret), version=2.0) 96 | # Ensure that version is below 2.1. Facebook has stated that FQL is 97 | # not present in this or future versions of the Graph API. 98 | if graph.get_version() < 2.1: 99 | # This is a tautology, but we are limited in what information 100 | # we can retrieve with a proper OAuth access token. 101 | fql_result = graph.fql( 102 | "SELECT app_id from application where app_id = %s" % 103 | self.app_id) 104 | self.assertEqual(fql_result["data"][0]["app_id"], str(self.app_id)) 105 | 106 | 107 | class TestAuthURL(FacebookTestCase): 108 | def test_auth_url(self): 109 | perms = ['email', 'birthday'] 110 | redirect_url = 'https://localhost/facebook/callback/' 111 | 112 | expected_url = 'https://www.facebook.com/dialog/oauth?' + urlencode( 113 | dict(client_id=self.app_id, 114 | redirect_uri=redirect_url, 115 | scope=','.join(perms))) 116 | actual_url = facebook.auth_url(self.app_id, redirect_url, perms=perms) 117 | 118 | # Since the order of the query string parameters might be 119 | # different in each URL, we cannot just compare them to each 120 | # other. 121 | expected_url_result = urlparse(expected_url) 122 | actual_url_result = urlparse(actual_url) 123 | expected_query = parse_qs(expected_url_result.query) 124 | actual_query = parse_qs(actual_url_result.query) 125 | 126 | self.assertEqual(actual_url_result.scheme, expected_url_result.scheme) 127 | self.assertEqual(actual_url_result.netloc, expected_url_result.netloc) 128 | self.assertEqual(actual_url_result.path, expected_url_result.path) 129 | self.assertEqual(actual_url_result.params, expected_url_result.params) 130 | self.assertEqual(actual_query, expected_query) 131 | 132 | 133 | if __name__ == '__main__': 134 | unittest.main() 135 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. linkcheck to check all external links for integrity 37 | echo. doctest to run all doctests embedded in the documentation if enabled 38 | goto end 39 | ) 40 | 41 | if "%1" == "clean" ( 42 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 43 | del /q /s %BUILDDIR%\* 44 | goto end 45 | ) 46 | 47 | if "%1" == "html" ( 48 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 49 | if errorlevel 1 exit /b 1 50 | echo. 51 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 52 | goto end 53 | ) 54 | 55 | if "%1" == "dirhtml" ( 56 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 57 | if errorlevel 1 exit /b 1 58 | echo. 59 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 60 | goto end 61 | ) 62 | 63 | if "%1" == "singlehtml" ( 64 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 68 | goto end 69 | ) 70 | 71 | if "%1" == "pickle" ( 72 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished; now you can process the pickle files. 76 | goto end 77 | ) 78 | 79 | if "%1" == "json" ( 80 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished; now you can process the JSON files. 84 | goto end 85 | ) 86 | 87 | if "%1" == "htmlhelp" ( 88 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can run HTML Help Workshop with the ^ 92 | .hhp project file in %BUILDDIR%/htmlhelp. 93 | goto end 94 | ) 95 | 96 | if "%1" == "qthelp" ( 97 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 98 | if errorlevel 1 exit /b 1 99 | echo. 100 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 101 | .qhcp project file in %BUILDDIR%/qthelp, like this: 102 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\FacebookSDKforPython.qhcp 103 | echo.To view the help file: 104 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\FacebookSDKforPython.ghc 105 | goto end 106 | ) 107 | 108 | if "%1" == "devhelp" ( 109 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 110 | if errorlevel 1 exit /b 1 111 | echo. 112 | echo.Build finished. 113 | goto end 114 | ) 115 | 116 | if "%1" == "epub" ( 117 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 118 | if errorlevel 1 exit /b 1 119 | echo. 120 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 121 | goto end 122 | ) 123 | 124 | if "%1" == "latex" ( 125 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 129 | goto end 130 | ) 131 | 132 | if "%1" == "text" ( 133 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The text files are in %BUILDDIR%/text. 137 | goto end 138 | ) 139 | 140 | if "%1" == "man" ( 141 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 145 | goto end 146 | ) 147 | 148 | if "%1" == "texinfo" ( 149 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 150 | if errorlevel 1 exit /b 1 151 | echo. 152 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 153 | goto end 154 | ) 155 | 156 | if "%1" == "gettext" ( 157 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 158 | if errorlevel 1 exit /b 1 159 | echo. 160 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 161 | goto end 162 | ) 163 | 164 | if "%1" == "changes" ( 165 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 166 | if errorlevel 1 exit /b 1 167 | echo. 168 | echo.The overview file is in %BUILDDIR%/changes. 169 | goto end 170 | ) 171 | 172 | if "%1" == "linkcheck" ( 173 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 174 | if errorlevel 1 exit /b 1 175 | echo. 176 | echo.Link check complete; look for any errors in the above output ^ 177 | or in %BUILDDIR%/linkcheck/output.txt. 178 | goto end 179 | ) 180 | 181 | if "%1" == "doctest" ( 182 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 183 | if errorlevel 1 exit /b 1 184 | echo. 185 | echo.Testing of doctests in the sources finished, look at the ^ 186 | results in %BUILDDIR%/doctest/output.txt. 187 | goto end 188 | ) 189 | 190 | :end 191 | -------------------------------------------------------------------------------- /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 | """ 18 | A barebones AppEngine application that uses Facebook for login. 19 | 20 | 1. Make sure you add a copy of facebook.py (from python-sdk/src/) 21 | into this directory so it can be imported. 22 | 2. Don't forget to tick Login With Facebook on your facebook app's 23 | dashboard and place the app's url wherever it is hosted 24 | 3. Place a random, unguessable string as a session secret below in 25 | config dict. 26 | 4. Fill app id and app secret. 27 | 5. Change the application name in app.yaml. 28 | 29 | """ 30 | FACEBOOK_APP_ID = "your app id" 31 | FACEBOOK_APP_SECRET = "your app secret" 32 | 33 | import facebook 34 | import webapp2 35 | import os 36 | import jinja2 37 | import urllib2 38 | 39 | from google.appengine.ext import db 40 | from webapp2_extras import sessions 41 | 42 | config = {} 43 | config['webapp2_extras.sessions'] = dict(secret_key='') 44 | 45 | 46 | class User(db.Model): 47 | id = db.StringProperty(required=True) 48 | created = db.DateTimeProperty(auto_now_add=True) 49 | updated = db.DateTimeProperty(auto_now=True) 50 | name = db.StringProperty(required=True) 51 | profile_url = db.StringProperty(required=True) 52 | access_token = db.StringProperty(required=True) 53 | 54 | 55 | class BaseHandler(webapp2.RequestHandler): 56 | """Provides access to the active Facebook user in self.current_user 57 | 58 | The property is lazy-loaded on first access, using the cookie saved 59 | by the Facebook JavaScript SDK to determine the user ID of the active 60 | user. See http://developers.facebook.com/docs/authentication/ for 61 | more information. 62 | """ 63 | @property 64 | def current_user(self): 65 | if self.session.get("user"): 66 | # User is logged in 67 | return self.session.get("user") 68 | else: 69 | # Either used just logged in or just saw the first page 70 | # We'll see here 71 | cookie = facebook.get_user_from_cookie(self.request.cookies, 72 | FACEBOOK_APP_ID, 73 | FACEBOOK_APP_SECRET) 74 | if cookie: 75 | # Okay so user logged in. 76 | # Now, check to see if existing user 77 | user = User.get_by_key_name(cookie["uid"]) 78 | if not user: 79 | # Not an existing user so get user info 80 | graph = facebook.GraphAPI(cookie["access_token"]) 81 | profile = graph.get_object("me") 82 | user = User( 83 | key_name=str(profile["id"]), 84 | id=str(profile["id"]), 85 | name=profile["name"], 86 | profile_url=profile["link"], 87 | access_token=cookie["access_token"] 88 | ) 89 | user.put() 90 | elif user.access_token != cookie["access_token"]: 91 | user.access_token = cookie["access_token"] 92 | user.put() 93 | # User is now logged in 94 | self.session["user"] = dict( 95 | name=user.name, 96 | profile_url=user.profile_url, 97 | id=user.id, 98 | access_token=user.access_token 99 | ) 100 | return self.session.get("user") 101 | return None 102 | 103 | def dispatch(self): 104 | """ 105 | This snippet of code is taken from the webapp2 framework documentation. 106 | See more at 107 | http://webapp-improved.appspot.com/api/webapp2_extras/sessions.html 108 | 109 | """ 110 | self.session_store = sessions.get_store(request=self.request) 111 | try: 112 | webapp2.RequestHandler.dispatch(self) 113 | finally: 114 | self.session_store.save_sessions(self.response) 115 | 116 | @webapp2.cached_property 117 | def session(self): 118 | """ 119 | This snippet of code is taken from the webapp2 framework documentation. 120 | See more at 121 | http://webapp-improved.appspot.com/api/webapp2_extras/sessions.html 122 | 123 | """ 124 | return self.session_store.get_session() 125 | 126 | 127 | class HomeHandler(BaseHandler): 128 | def get(self): 129 | template = jinja_environment.get_template('example.html') 130 | self.response.out.write(template.render(dict( 131 | facebook_app_id=FACEBOOK_APP_ID, 132 | current_user=self.current_user 133 | ))) 134 | 135 | def post(self): 136 | url = self.request.get('url') 137 | file = urllib2.urlopen(url) 138 | graph = facebook.GraphAPI(self.current_user['access_token']) 139 | response = graph.put_photo(file, "Test Image") 140 | photo_url = ("http://www.facebook.com/" 141 | "photo.php?fbid={0}".format(response['id'])) 142 | self.redirect(str(photo_url)) 143 | 144 | 145 | class LogoutHandler(BaseHandler): 146 | def get(self): 147 | if self.current_user is not None: 148 | self.session['user'] = None 149 | 150 | self.redirect('/') 151 | 152 | jinja_environment = jinja2.Environment( 153 | loader=jinja2.FileSystemLoader(os.path.dirname(__file__)) 154 | ) 155 | 156 | app = webapp2.WSGIApplication( 157 | [('/', HomeHandler), ('/logout', LogoutHandler)], 158 | debug=True, 159 | config=config 160 | ) 161 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 19 | help: 20 | @echo "Please use \`make ' where is one of" 21 | @echo " html to make standalone HTML files" 22 | @echo " dirhtml to make HTML files named index.html in directories" 23 | @echo " singlehtml to make a single large HTML file" 24 | @echo " pickle to make pickle files" 25 | @echo " json to make JSON files" 26 | @echo " htmlhelp to make HTML files and a HTML help project" 27 | @echo " qthelp to make HTML files and a qthelp project" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 31 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/FacebookSDKforPython.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/FacebookSDKforPython.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/FacebookSDKforPython" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/FacebookSDKforPython" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /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, 84 | redirect_uri=self.request.path_url) 85 | if verification_code: 86 | args["client_secret"] = FACEBOOK_APP_SECRET 87 | args["code"] = verification_code 88 | response = cgi.parse_qs(urllib.urlopen( 89 | "https://graph.facebook.com/oauth/access_token?" + 90 | urllib.urlencode(args)).read()) 91 | access_token = response["access_token"][-1] 92 | 93 | # Download the user profile and cache a local instance of the 94 | # basic profile info 95 | profile = json.load(urllib.urlopen( 96 | "https://graph.facebook.com/me?" + 97 | urllib.urlencode(dict(access_token=access_token)))) 98 | user = User(key_name=str(profile["id"]), id=str(profile["id"]), 99 | name=profile["name"], access_token=access_token, 100 | profile_url=profile["link"]) 101 | user.put() 102 | set_cookie(self.response, "fb_user", str(profile["id"]), 103 | expires=time.time() + 30 * 86400) 104 | self.redirect("/") 105 | else: 106 | self.redirect( 107 | "https://graph.facebook.com/oauth/authorize?" + 108 | urllib.urlencode(args)) 109 | 110 | 111 | class LogoutHandler(BaseHandler): 112 | def get(self): 113 | set_cookie(self.response, "fb_user", "", expires=time.time() - 86400) 114 | self.redirect("/") 115 | 116 | 117 | def set_cookie(response, name, value, domain=None, path="/", expires=None): 118 | """Generates and signs a cookie for the give name/value""" 119 | timestamp = str(int(time.time())) 120 | value = base64.b64encode(value) 121 | signature = cookie_signature(value, timestamp) 122 | cookie = Cookie.BaseCookie() 123 | cookie[name] = "|".join([value, timestamp, signature]) 124 | cookie[name]["path"] = path 125 | if domain: 126 | cookie[name]["domain"] = domain 127 | if expires: 128 | cookie[name]["expires"] = email.utils.formatdate( 129 | expires, localtime=False, usegmt=True) 130 | response.headers._headers.append(("Set-Cookie", cookie.output()[12:])) 131 | 132 | 133 | def parse_cookie(value): 134 | """Parses and verifies a cookie value from set_cookie""" 135 | if not value: 136 | return None 137 | parts = value.split("|") 138 | if len(parts) != 3: 139 | return None 140 | if cookie_signature(parts[0], parts[1]) != parts[2]: 141 | logging.warning("Invalid cookie signature %r", value) 142 | return None 143 | timestamp = int(parts[1]) 144 | if timestamp < time.time() - 30 * 86400: 145 | logging.warning("Expired cookie %r", value) 146 | return None 147 | try: 148 | return base64.b64decode(parts[0]).strip() 149 | except: 150 | return None 151 | 152 | 153 | def cookie_signature(*parts): 154 | """Generates a cookie signature. 155 | 156 | We use the Facebook app secret since it is different for every app (so 157 | people using this example don't accidentally all use the same secret). 158 | """ 159 | hash = hmac.new(FACEBOOK_APP_SECRET, digestmod=hashlib.sha1) 160 | for part in parts: 161 | hash.update(part) 162 | return hash.hexdigest() 163 | 164 | 165 | def main(): 166 | util.run_wsgi_app(webapp.WSGIApplication([ 167 | (r"/", HomeHandler), 168 | (r"/auth/login", LoginHandler), 169 | (r"/auth/logout", LogoutHandler), 170 | ])) 171 | 172 | 173 | if __name__ == "__main__": 174 | main() 175 | -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | ============= 2 | API Reference 3 | ============= 4 | 5 | This page contains specific information on the SDK's classes, methods and 6 | functions. 7 | 8 | class facebook.GraphAPI 9 | ======================= 10 | 11 | A client for the Facebook Graph API. The Graph API is made up of the objects or 12 | nodes in Facebook (e.g., people, pages, events, photos) and the connections or 13 | edges between them (e.g., friends, photo tags, and event RSVPs). This client 14 | provides access to those primitive types in a generic way. 15 | 16 | You can read more about `Facebook's Graph API here`_. 17 | 18 | .. _Facebook's Graph API here: https://developers.facebook.com/docs/graph-api 19 | 20 | **Parameters** 21 | 22 | * ``access_token`` – A ``string`` that identifies a user, app, or page and can 23 | be used by the app to make graph API calls. 24 | `Read more about access tokens here`_. 25 | * ``timeout`` - A ``float`` describing (in seconds) how long the client will 26 | waiting for a response from Facebook's servers. `See more here`_. 27 | * ``version`` - A ``string`` describing the `version of Facebook's Graph API to 28 | use`_. Valid API versions are ``1.0``, ``2.0``, ``2.1`` and ``2.2``. The 29 | default version is ``1.0`` and is used if the version keyword argument is not 30 | provided. 31 | 32 | .. _Read more about access tokens here: https://developers.facebook.com/docs/facebook-login/access-tokens 33 | .. _See more here: http://docs.python-requests.org/en/latest/user/quickstart/#timeouts 34 | .. _version of Facebook's Graph API to use: https://developers.facebook.com/docs/apps/versions 35 | 36 | **Example** 37 | 38 | .. code-block:: python 39 | 40 | import facebook 41 | 42 | graph = facebook.GraphAPI(access_token='your_token', version='2.2') 43 | 44 | Methods 45 | ------- 46 | 47 | get_object(id, \*\*args) 48 | ^^^^^^^^^^^^^^^^^^^^^^^^ 49 | 50 | Returns the given object from the graph as a ``dict``. A list of 51 | `supported objects can be found here`_. 52 | 53 | .. _supported objects can be found here: https://developers.facebook.com/docs/graph-api/reference/ 54 | 55 | **Parameters** 56 | 57 | * ``id`` – A ``string`` that is a unique ID for that particular resource. 58 | 59 | **Example** 60 | 61 | .. code-block:: python 62 | 63 | post = graph.get_object(id='post_id') 64 | print post['message'] 65 | 66 | 67 | get_objects(id, \*\*args) 68 | ^^^^^^^^^^^^^^^^^^^^^^^^^ 69 | 70 | Returns all of the given objects from the graph as a ``dict``. Each given ID 71 | maps to an object. 72 | 73 | **Parameters** 74 | 75 | * ``ids`` – A ``list`` containing IDs for multiple resources. 76 | 77 | **Example** 78 | 79 | .. code-block:: python 80 | 81 | post_ids = ['post_id_1', 'post_id_2'] 82 | posts = graph.get_objects(ids=post_ids) 83 | 84 | # Each given id maps to an object. 85 | for post_id in post_ids: 86 | print posts[post_id]['created_time'] 87 | 88 | 89 | get_connections(id, connection_name, \*\*args) 90 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 91 | 92 | Returns all connections for a given object as a ``dict``. 93 | 94 | **Parameters** 95 | 96 | * ``id`` – A ``string`` that is a unique ID for that particular resource. 97 | * ``connection_name`` - A ``string`` that specifies the connection or edge 98 | between objects, e.g., feed, friends, groups, likes, posts. If left empty, 99 | ``get_connections`` will simply return the authenticated user's basic 100 | information. 101 | 102 | **Example** 103 | 104 | .. code-block:: python 105 | 106 | # Get all of the authenticated user's friends 107 | friends = graph.get_connections(id='me', connection_name='friends') 108 | 109 | # Get all the comments from a post 110 | comments = graph.get_connections(id='post_id', connection_name='comments') 111 | 112 | 113 | put_object(parent_object, connection_name, \*\*data) 114 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 115 | 116 | Writes the given object to the graph, connected to the given parent. 117 | 118 | **Parameters** 119 | 120 | * ``parent_object`` – A ``string`` that is a unique ID for that particular 121 | resource. The ``parent_object`` is the parent of a connection or edge. E.g., 122 | profile is the parent of a feed, and a post is the parent of a comment. 123 | * ``connection_name`` - A ``string`` that specifies the connection or edge 124 | between objects, e.g., feed, friends, groups, likes, posts. 125 | 126 | **Example** 127 | 128 | .. code-block:: python 129 | 130 | # Writes 'Hello, world' to the active user's wall. 131 | graph.put_object(parent_object='me', connection_name='feed', 132 | message='Hello, world') 133 | 134 | # Writes a comment on a post 135 | graph.put_object(parent_object='post_id', connection_name='comments', 136 | message='First!') 137 | 138 | 139 | put_wall_post(message, attachment, profile_id) 140 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 141 | 142 | Writes a wall post to the given profile's wall. It defaults to writing to the 143 | authenticated user's wall if no ``profile_id`` is specified. 144 | 145 | **Parameters** 146 | 147 | * ``message`` - A ``string`` that will be posted to the user's wall. 148 | * ``attachment`` - A ``dict`` that adds a structured attachment to the message 149 | being posted to the Wall. If you are sharing a URL, you will want to use the 150 | ``attachment`` parameter so that a thumbnail preview appears in the post. It 151 | should be a ``dict`` of the form: 152 | 153 | .. code-block:: python 154 | 155 | attachment = { 156 | 'name': '' 157 | 'link': '', 158 | 'caption': '', 159 | 'description': '', 160 | 'picture': '' 161 | } 162 | 163 | * ``profile_id`` - A ``string`` that is a unique ID for that particular user. 164 | Defaults to the authenticated user's wall. 165 | 166 | **Example** 167 | 168 | .. code-block:: python 169 | 170 | attachment = { 171 | 'name': 'Link name' 172 | 'link': 'http://www.example.com/', 173 | 'caption': 'Check out this example', 174 | 'description': 'This is a longer description of the attachment', 175 | 'picture': 'http://www.example.com/thumbnail.jpg' 176 | } 177 | 178 | graph.put_wall_post(message='Check this out...', attachment=attachment) 179 | 180 | 181 | put_comment(object_id, message) 182 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 183 | 184 | Writes the given message as a comment on an object. 185 | 186 | **Parameters** 187 | 188 | * ``object_id`` - A ``string`` that is a unique id for a particular resource. 189 | * ``message`` - A ``string`` that will be posted as the comment. 190 | 191 | **Example** 192 | 193 | .. code-block:: python 194 | 195 | graph.put_comment(object_id='post_id', message='Great post...') 196 | 197 | 198 | put_like(object_id) 199 | ^^^^^^^^^^^^^^^^^^^ 200 | 201 | Writes a like to the given object. 202 | 203 | **Parameters** 204 | 205 | * ``object_id`` - A ``string`` that is a unique id for a particular resource. 206 | 207 | **Example** 208 | 209 | .. code-block:: python 210 | 211 | graph.put_like(object_id='comment_id') 212 | 213 | 214 | put_photo(image, message, album_id, \*\*kwargs) 215 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 216 | 217 | Uploads an image using multipart/form-data. 218 | 219 | **Parameters** 220 | 221 | * ``image`` - An image of the ``file`` type 222 | * ``message`` - A ``string`` that will caption the image 223 | * ``album_id`` - A ``string`` that is a unique id for an album. If no 224 | ``album_id`` is provided, the photo posts to /me/photos which uses, or 225 | creates and uses, an album for your application. 226 | 227 | **Example** 228 | 229 | .. code-block:: python 230 | 231 | tags = json.dumps([ 232 | {'x':50, 'y':50, 'tag_uid':12345}, 233 | {'x':10, 'y':60, 'tag_text':'a turtle'} 234 | ]) 235 | graph.put_photo(image=open('img.jpg'), message='Look at this cool photo!', 236 | tags=tags) 237 | 238 | delete_object(id) 239 | ^^^^^^^^^^^^^^^^^ 240 | 241 | Deletes the object with the given ID from the graph. 242 | 243 | **Parameters** 244 | 245 | * ``id`` - A ``string`` that is a unique ID for a particular resource. 246 | 247 | **Example** 248 | 249 | .. code-block:: python 250 | 251 | graph.delete_object(id='post_id') 252 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Facebook SDK for Python documentation build configuration file, created by 4 | # sphinx-quickstart on Mon Oct 15 01:01:28 2012. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | exec(open("../facebook/version.py").read()) 17 | 18 | # If extensions (or modules to document with autodoc) are in another directory, 19 | # add these directories to sys.path here. If the directory is relative to the 20 | # documentation root, use os.path.abspath to make it absolute, like shown here. 21 | #sys.path.insert(0, os.path.abspath('.')) 22 | 23 | # -- General configuration ----------------------------------------------------- 24 | 25 | # If your documentation needs a minimal Sphinx version, state it here. 26 | #needs_sphinx = '1.0' 27 | 28 | # Add any Sphinx extension module names here, as strings. They can be extensions 29 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 30 | extensions = [] 31 | 32 | # Add any paths that contain templates here, relative to this directory. 33 | templates_path = ['_templates'] 34 | 35 | # The suffix of source filenames. 36 | source_suffix = '.rst' 37 | 38 | # The encoding of source files. 39 | #source_encoding = 'utf-8-sig' 40 | 41 | # The master toctree document. 42 | master_doc = 'index' 43 | 44 | # General information about the project. 45 | project = u'Facebook SDK for Python' 46 | copyright = u'2010 Facebook, 2010-2014 Python for Facebook developers' 47 | 48 | # The version info for the project you're documenting, acts as replacement for 49 | # |version| and |release|, also used in various other places throughout the 50 | # built documents. 51 | # 52 | # The short X.Y version. 53 | version = '.'.join(__version__.split('.')[:2]) 54 | # The full version, including alpha/beta/rc tags. 55 | release = __version__ 56 | 57 | # The language for content autogenerated by Sphinx. Refer to documentation 58 | # for a list of supported languages. 59 | #language = None 60 | 61 | # There are two options for replacing |today|: either, you set today to some 62 | # non-false value, then it is used: 63 | #today = '' 64 | # Else, today_fmt is used as the format for a strftime call. 65 | #today_fmt = '%B %d, %Y' 66 | 67 | # List of patterns, relative to source directory, that match files and 68 | # directories to ignore when looking for source files. 69 | exclude_patterns = ['_build'] 70 | 71 | # The reST default role (used for this markup: `text`) to use for all documents. 72 | #default_role = None 73 | 74 | # If true, '()' will be appended to :func: etc. cross-reference text. 75 | #add_function_parentheses = True 76 | 77 | # If true, the current module name will be prepended to all description 78 | # unit titles (such as .. function::). 79 | #add_module_names = True 80 | 81 | # If true, sectionauthor and moduleauthor directives will be shown in the 82 | # output. They are ignored by default. 83 | #show_authors = False 84 | 85 | # The name of the Pygments (syntax highlighting) style to use. 86 | pygments_style = 'sphinx' 87 | 88 | # A list of ignored prefixes for module index sorting. 89 | #modindex_common_prefix = [] 90 | 91 | 92 | # -- Options for HTML output --------------------------------------------------- 93 | 94 | # The theme to use for HTML and HTML Help pages. See the documentation for 95 | # a list of builtin themes. 96 | html_theme = 'default' 97 | 98 | # Theme options are theme-specific and customize the look and feel of a theme 99 | # further. For a list of options available for each theme, see the 100 | # documentation. 101 | #html_theme_options = {} 102 | 103 | # Add any paths that contain custom themes here, relative to this directory. 104 | #html_theme_path = [] 105 | 106 | # The name for this set of Sphinx documents. If None, it defaults to 107 | # " v documentation". 108 | #html_title = None 109 | 110 | # A shorter title for the navigation bar. Default is the same as html_title. 111 | #html_short_title = None 112 | 113 | # The name of an image file (relative to this directory) to place at the top 114 | # of the sidebar. 115 | #html_logo = None 116 | 117 | # The name of an image file (within the static path) to use as favicon of the 118 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 119 | # pixels large. 120 | #html_favicon = None 121 | 122 | # Add any paths that contain custom static files (such as style sheets) here, 123 | # relative to this directory. They are copied after the builtin static files, 124 | # so a file named "default.css" will overwrite the builtin "default.css". 125 | html_static_path = ['_static'] 126 | 127 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 128 | # using the given strftime format. 129 | #html_last_updated_fmt = '%b %d, %Y' 130 | 131 | # If true, SmartyPants will be used to convert quotes and dashes to 132 | # typographically correct entities. 133 | #html_use_smartypants = True 134 | 135 | # Custom sidebar templates, maps document names to template names. 136 | #html_sidebars = {} 137 | 138 | # Additional templates that should be rendered to pages, maps page names to 139 | # template names. 140 | #html_additional_pages = {} 141 | 142 | # If false, no module index is generated. 143 | #html_domain_indices = True 144 | 145 | # If false, no index is generated. 146 | #html_use_index = True 147 | 148 | # If true, the index is split into individual pages for each letter. 149 | #html_split_index = False 150 | 151 | # If true, links to the reST sources are added to the pages. 152 | #html_show_sourcelink = True 153 | 154 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 155 | #html_show_sphinx = True 156 | 157 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 158 | #html_show_copyright = True 159 | 160 | # If true, an OpenSearch description file will be output, and all pages will 161 | # contain a tag referring to it. The value of this option must be the 162 | # base URL from which the finished HTML is served. 163 | #html_use_opensearch = '' 164 | 165 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 166 | #html_file_suffix = None 167 | 168 | # Output file base name for HTML help builder. 169 | htmlhelp_basename = 'FacebookSDKforPythondoc' 170 | 171 | 172 | # -- Options for LaTeX output -------------------------------------------------- 173 | 174 | latex_elements = { 175 | # The paper size ('letterpaper' or 'a4paper'). 176 | #'papersize': 'letterpaper', 177 | 178 | # The font size ('10pt', '11pt' or '12pt'). 179 | #'pointsize': '10pt', 180 | 181 | # Additional stuff for the LaTeX preamble. 182 | #'preamble': '', 183 | } 184 | 185 | # Grouping the document tree into LaTeX files. List of tuples 186 | # (source start file, target name, title, author, documentclass [howto/manual]). 187 | latex_documents = [ 188 | ('index', 'FacebookSDKforPython.tex', u'Facebook SDK for Python Documentation', 189 | u'Facebook, Python for Facebook developers', 'manual'), 190 | ] 191 | 192 | # The name of an image file (relative to this directory) to place at the top of 193 | # the title page. 194 | #latex_logo = None 195 | 196 | # For "manual" documents, if this is true, then toplevel headings are parts, 197 | # not chapters. 198 | #latex_use_parts = False 199 | 200 | # If true, show page references after internal links. 201 | #latex_show_pagerefs = False 202 | 203 | # If true, show URL addresses after external links. 204 | #latex_show_urls = False 205 | 206 | # Documents to append as an appendix to all manuals. 207 | #latex_appendices = [] 208 | 209 | # If false, no module index is generated. 210 | #latex_domain_indices = True 211 | 212 | 213 | # -- Options for manual page output -------------------------------------------- 214 | 215 | # One entry per manual page. List of tuples 216 | # (source start file, name, description, authors, manual section). 217 | man_pages = [ 218 | ('index', 'facebooksdkforpython', u'Facebook SDK for Python Documentation', 219 | [u'Facebook, Python for Facebook developers'], 1) 220 | ] 221 | 222 | # If true, show URL addresses after external links. 223 | #man_show_urls = False 224 | 225 | 226 | # -- Options for Texinfo output ------------------------------------------------ 227 | 228 | # Grouping the document tree into Texinfo files. List of tuples 229 | # (source start file, target name, title, author, 230 | # dir menu entry, description, category) 231 | texinfo_documents = [ 232 | ('index', 'FacebookSDKforPython', u'Facebook SDK for Python Documentation', 233 | u'Facebook, Python for Facebook developers', 'FacebookSDKforPython', 'One line description of project.', 234 | 'Miscellaneous'), 235 | ] 236 | 237 | # Documents to append as an appendix to all manuals. 238 | #texinfo_appendices = [] 239 | 240 | # If false, no module index is generated. 241 | #texinfo_domain_indices = True 242 | 243 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 244 | #texinfo_show_urls = 'footnote' 245 | -------------------------------------------------------------------------------- /facebook/__init__.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 20 | official Facebook JavaScript SDK, which is the canonical way to 21 | implement 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 hashlib 37 | import hmac 38 | import base64 39 | import requests 40 | import json 41 | import re 42 | 43 | try: 44 | from urllib.parse import parse_qs, urlencode 45 | except ImportError: 46 | from urlparse import parse_qs 47 | from urllib import urlencode 48 | 49 | from . import version 50 | 51 | 52 | __version__ = version.__version__ 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 59 | documentation for the API. 60 | 61 | The Graph API is made up of the objects in Facebook (e.g., people, 62 | pages, events, photos) and the connections between them (e.g., 63 | friends, photo tags, and event RSVPs). This client provides access 64 | to those primitive types in a generic way. For example, given an 65 | OAuth access token, this will fetch the profile of the active user 66 | and the list 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 77 | http://developers.facebook.com/docs/authentication/ 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 | """ 84 | 85 | def __init__(self, access_token=None, timeout=None, version=None): 86 | # The default version is only used if the version kwarg does not exist. 87 | default_version = "1.0" 88 | valid_API_versions = ["1.0", "2.0", "2.1", "2.2"] 89 | 90 | self.access_token = access_token 91 | self.timeout = timeout 92 | 93 | if version: 94 | version_regex = re.compile("^\d\.\d$") 95 | match = version_regex.search(str(version)) 96 | if match is not None: 97 | if str(version) not in valid_API_versions: 98 | raise GraphAPIError("Valid API versions are " + 99 | str(valid_API_versions).strip('[]')) 100 | else: 101 | self.version = "v" + str(version) 102 | else: 103 | raise GraphAPIError("Version number should be in the" 104 | " following format: #.# (e.g. 1.0).") 105 | else: 106 | self.version = "v" + default_version 107 | 108 | def get_object(self, id, **args): 109 | """Fetchs the given object from the graph.""" 110 | return self.request(self.version + "/" + id, args) 111 | 112 | def get_objects(self, ids, **args): 113 | """Fetchs all of the given object from the graph. 114 | 115 | We return a map from ID to object. If any of the IDs are 116 | invalid, we raise an exception. 117 | """ 118 | args["ids"] = ",".join(ids) 119 | return self.request(self.version + "/", args) 120 | 121 | def get_connections(self, id, connection_name, **args): 122 | """Fetchs the connections for given object.""" 123 | return self.request( 124 | self.version + "/" + id + "/" + connection_name, args) 125 | 126 | def put_object(self, parent_object, connection_name, **data): 127 | """Writes the given object to the graph, connected to the given parent. 128 | 129 | For example, 130 | 131 | graph.put_object("me", "feed", message="Hello, world") 132 | 133 | writes "Hello, world" to the active user's wall. Likewise, this 134 | will comment on a the first post of the active user's feed: 135 | 136 | feed = graph.get_connections("me", "feed") 137 | post = feed["data"][0] 138 | graph.put_object(post["id"], "comments", message="First!") 139 | 140 | See http://developers.facebook.com/docs/api#publishing for all 141 | of the supported writeable objects. 142 | 143 | Certain write operations require extended permissions. For 144 | example, publishing to a user's feed requires the 145 | "publish_actions" permission. See 146 | http://developers.facebook.com/docs/publishing/ for details 147 | about publishing permissions. 148 | 149 | """ 150 | assert self.access_token, "Write operations require an access token" 151 | return self.request( 152 | self.version + "/" + parent_object + "/" + connection_name, 153 | post_args=data, 154 | method="POST") 155 | 156 | def put_wall_post(self, message, attachment={}, profile_id="me"): 157 | """Writes a wall post to the given profile's wall. 158 | 159 | We default to writing to the authenticated user's wall if no 160 | profile_id is specified. 161 | 162 | attachment adds a structured attachment to the status message 163 | being posted to the Wall. It should be a dictionary of the form: 164 | 165 | {"name": "Link name" 166 | "link": "http://www.example.com/", 167 | "caption": "{*actor*} posted a new review", 168 | "description": "This is a longer description of the attachment", 169 | "picture": "http://www.example.com/thumbnail.jpg"} 170 | 171 | """ 172 | return self.put_object(profile_id, "feed", message=message, 173 | **attachment) 174 | 175 | def put_comment(self, object_id, message): 176 | """Writes the given comment on the given post.""" 177 | return self.put_object(object_id, "comments", message=message) 178 | 179 | def put_like(self, object_id): 180 | """Likes the given post.""" 181 | return self.put_object(object_id, "likes") 182 | 183 | def delete_object(self, id): 184 | """Deletes the object with the given ID from the graph.""" 185 | self.request(self.version + "/" + id, method="DELETE") 186 | 187 | def delete_request(self, user_id, request_id): 188 | """Deletes the Request with the given ID for the given user.""" 189 | self.request("%s_%s" % (request_id, user_id), method="DELETE") 190 | 191 | def put_photo(self, image, message=None, album_id=None, **kwargs): 192 | """Uploads an image using multipart/form-data. 193 | 194 | image=File like object for the image 195 | message=Caption for your image 196 | album_id=None posts to /me/photos which uses or creates and uses 197 | an album for your application. 198 | 199 | """ 200 | object_id = album_id or "me" 201 | kwargs.update({"message": message}) 202 | self.request(self.version + "/" + object_id, 203 | post_args=kwargs, 204 | files={"file": image}, 205 | method="POST") 206 | 207 | def get_version(self): 208 | """Fetches the current version number of the Graph API being used.""" 209 | args = {"access_token": self.access_token} 210 | try: 211 | response = requests.request("GET", 212 | "https://graph.facebook.com/" + 213 | self.version + "/me", 214 | params=args, 215 | timeout=self.timeout) 216 | except requests.HTTPError as e: 217 | response = json.loads(e.read()) 218 | raise GraphAPIError(response) 219 | 220 | try: 221 | headers = response.headers 222 | version = headers["facebook-api-version"].replace("v", "") 223 | return float(version) 224 | except Exception: 225 | raise GraphAPIError("API version number not available") 226 | 227 | def request( 228 | self, path, args=None, post_args=None, files=None, method=None): 229 | """Fetches the given path in the Graph API. 230 | 231 | We translate args to a valid query string. If post_args is 232 | given, we send a POST request to the given path with the given 233 | arguments. 234 | 235 | """ 236 | args = args or {} 237 | 238 | if self.access_token: 239 | if post_args is not None: 240 | post_args["access_token"] = self.access_token 241 | else: 242 | args["access_token"] = self.access_token 243 | 244 | try: 245 | response = requests.request(method or "GET", 246 | "https://graph.facebook.com/" + 247 | path, 248 | timeout=self.timeout, 249 | params=args, 250 | data=post_args, 251 | files=files) 252 | except requests.HTTPError as e: 253 | response = json.loads(e.read()) 254 | raise GraphAPIError(response) 255 | 256 | headers = response.headers 257 | if 'json' in headers['content-type']: 258 | result = response.json() 259 | elif 'image/' in headers['content-type']: 260 | mimetype = headers['content-type'] 261 | result = {"data": response.content, 262 | "mime-type": mimetype, 263 | "url": response.url} 264 | elif "access_token" in parse_qs(response.text): 265 | query_str = parse_qs(response.text) 266 | if "access_token" in query_str: 267 | result = {"access_token": query_str["access_token"][0]} 268 | if "expires" in query_str: 269 | result["expires"] = query_str["expires"][0] 270 | else: 271 | raise GraphAPIError(response.json()) 272 | else: 273 | raise GraphAPIError('Maintype was not text, image, or querystring') 274 | 275 | if result and isinstance(result, dict) and result.get("error"): 276 | raise GraphAPIError(result) 277 | return result 278 | 279 | def fql(self, query): 280 | """FQL query. 281 | 282 | Example query: "SELECT affiliations FROM user WHERE uid = me()" 283 | 284 | """ 285 | return self.request(self.version + "/" + "fql", {"q": query}) 286 | 287 | def get_app_access_token(self, app_id, app_secret): 288 | """Get the application's access token as a string.""" 289 | args = {'grant_type': 'client_credentials', 290 | 'client_id': app_id, 291 | 'client_secret': app_secret} 292 | 293 | return self.request("oauth/access_token", args=args)["access_token"] 294 | 295 | def get_access_token_from_code( 296 | self, code, redirect_uri, app_id, app_secret): 297 | """Get an access token from the "code" returned from an OAuth dialog. 298 | 299 | Returns a dict containing the user-specific access token and its 300 | expiration date (if applicable). 301 | 302 | """ 303 | args = { 304 | "code": code, 305 | "redirect_uri": redirect_uri, 306 | "client_id": app_id, 307 | "client_secret": app_secret} 308 | 309 | return self.request("oauth/access_token", args) 310 | 311 | def extend_access_token(self, app_id, app_secret): 312 | """ 313 | Extends the expiration time of a valid OAuth access token. See 314 | 316 | 317 | """ 318 | args = { 319 | "client_id": app_id, 320 | "client_secret": app_secret, 321 | "grant_type": "fb_exchange_token", 322 | "fb_exchange_token": self.access_token, 323 | } 324 | 325 | return self.request("oauth/access_token", args=args) 326 | 327 | 328 | class GraphAPIError(Exception): 329 | def __init__(self, result): 330 | self.result = result 331 | try: 332 | self.type = result["error_code"] 333 | except: 334 | self.type = "" 335 | 336 | # OAuth 2.0 Draft 10 337 | try: 338 | self.message = result["error_description"] 339 | except: 340 | # OAuth 2.0 Draft 00 341 | try: 342 | self.message = result["error"]["message"] 343 | except: 344 | # REST server style 345 | try: 346 | self.message = result["error_msg"] 347 | except: 348 | self.message = result 349 | 350 | Exception.__init__(self, self.message) 351 | 352 | 353 | def get_user_from_cookie(cookies, app_id, app_secret): 354 | """Parses the cookie set by the official Facebook JavaScript SDK. 355 | 356 | cookies should be a dictionary-like object mapping cookie names to 357 | cookie values. 358 | 359 | If the user is logged in via Facebook, we return a dictionary with 360 | the keys "uid" and "access_token". The former is the user's 361 | Facebook ID, and the latter can be used to make authenticated 362 | requests to the Graph API. If the user is not logged in, we 363 | return None. 364 | 365 | Download the official Facebook JavaScript SDK at 366 | http://github.com/facebook/connect-js/. Read more about Facebook 367 | authentication at 368 | http://developers.facebook.com/docs/authentication/. 369 | 370 | """ 371 | cookie = cookies.get("fbsr_" + app_id, "") 372 | if not cookie: 373 | return None 374 | parsed_request = parse_signed_request(cookie, app_secret) 375 | if not parsed_request: 376 | return None 377 | try: 378 | result = get_access_token_from_code(parsed_request["code"], "", 379 | app_id, app_secret) 380 | except GraphAPIError: 381 | return None 382 | result["uid"] = parsed_request["user_id"] 383 | return result 384 | 385 | 386 | def parse_signed_request(signed_request, app_secret): 387 | """ Return dictionary with signed request data. 388 | 389 | We return a dictionary containing the information in the 390 | signed_request. This includes a user_id if the user has authorised 391 | your application, as well as any information requested. 392 | 393 | If the signed_request is malformed or corrupted, False is returned. 394 | 395 | """ 396 | try: 397 | encoded_sig, payload = map(str, signed_request.split('.', 1)) 398 | 399 | sig = base64.urlsafe_b64decode(encoded_sig + "=" * 400 | ((4 - len(encoded_sig) % 4) % 4)) 401 | data = base64.urlsafe_b64decode(payload + "=" * 402 | ((4 - len(payload) % 4) % 4)) 403 | except IndexError: 404 | # Signed request was malformed. 405 | return False 406 | except TypeError: 407 | # Signed request had a corrupted payload. 408 | return False 409 | 410 | data = json.loads(data) 411 | if data.get('algorithm', '').upper() != 'HMAC-SHA256': 412 | return False 413 | 414 | # HMAC can only handle ascii (byte) strings 415 | # http://bugs.python.org/issue5285 416 | app_secret = app_secret.encode('ascii') 417 | payload = payload.encode('ascii') 418 | 419 | expected_sig = hmac.new(app_secret, 420 | msg=payload, 421 | digestmod=hashlib.sha256).digest() 422 | if sig != expected_sig: 423 | return False 424 | 425 | return data 426 | 427 | 428 | def auth_url(app_id, canvas_url, perms=None, **kwargs): 429 | url = "https://www.facebook.com/dialog/oauth?" 430 | kvps = {'client_id': app_id, 'redirect_uri': canvas_url} 431 | if perms: 432 | kvps['scope'] = ",".join(perms) 433 | kvps.update(kwargs) 434 | return url + urlencode(kvps) 435 | 436 | 437 | def get_access_token_from_code(code, redirect_uri, app_id, app_secret): 438 | return GraphAPI().get_access_token_from_code( 439 | code, redirect_uri, app_id, app_secret) 440 | 441 | 442 | def get_app_access_token(app_id, app_secret): 443 | return GraphAPI().get_app_access_token(app_id, app_secret) 444 | --------------------------------------------------------------------------------