UNMAINTAINED: A library and REST API that serves Facebook and Twitter user data in PortableContacts format
Python HTML JavaScript CSS
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
oauth_dropins @ 071426e
static
templates
.gitignore
.gitmodules
README.md
alltests.py
app.py
app.yaml
app.yaml.facebook
app.yaml.twitter
appengine_config.py
facebook.py
facebook_test.py
poco.py
poco_test.py
source.py
twitter.py
twitter_test.py
webutil

README.md

portablecontacts-unofficial PortableContacts

This project is unmaintained, and Portable Contacts seems to be dead. OpenSocial absorbed it, then W3C's Social Web group absorbed OpenSocial, so OpenSocial is probably dead too. RIP. Happy hacking!

About

This is a library and REST API that converts Facebook and Twitter user data to the Portable Contacts format.

The REST API used to be hosted at these sites, but it's been turned down. Feel free to host it yourself!

It's part of a suite of projects that implement the OStatus federation protocols for the major social networks. The other projects include activitystreams-, salmon-, webfinger-, and ostatus-unofficial.

Google isn't included because it already serves Gmail contacts and user data in PortableContacts format at gmail.com.

License: This project is placed in the public domain.

Using

The library and REST API are both based on the Portable Contacts 1.0 Draft C spec

Let's start with an example. This code using the library:

from portablecontacts_unofficial import twitter
...
tw = twitter.Twitter(ACCESS_TOKEN_KEY, ACCESS_TOKEN_SECRET)
tw.get_contacts()

is equivalent to this HTTP GET request:

https://twitter-poco.appspot.com/poco/@me/@all/
  ?access_token_key=ACCESS_TOKEN_KEY&access_token_secret=ACCESS_TOKEN_SECRET

They return the Twitter users that the authenticated user follows. Here's the JSON output:

{
  "itemsPerPage": 10,
  "startIndex": 0,
  "totalResults": 12
  "items": [{
    "id": "139199211",
    "displayName": "Ryan Barrett",
    "name": {"formatted": "Ryan Barrett"},
    "accounts": [{"domain": "twitter.com",
                  "userid": "139199211",
                  "username": "snarfed_org",
                  }],
    "addresses": [{"formatted": "San Francisco, CA", "type": "home", }],
    "photos": [{"value": "http://a1.twimg.com/profile_images/866165047/ryan_normal.jpg",
                "primary": "true",
                }],
    "urls": [{"value": "http://snarfed.org/",
              "type": "home",
              }],
    ...
  }, ...]
  ...
}

The request parameters are the same for both, all optional: group_id may be @all or @self. USER_ID may be a specific user id or unset for all users.

Paging is supported via the startIndex and count parameters. They're self explanatory, and described in detail in the OpenSearch spec.

Output data is PoCo JSON formatted response object dicts, which put the contacts in a top-level entry list alongside itemsPerPage, totalCount, etc. fields.

Most Facebook requests and all Twitter requests will need OAuth access tokens. If you're using Python on Google App Engine, oauth-dropins is an easy way to add OAuth client flows for these sites. Otherwise, here are the sites' authentication docs: Facebook, Twitter.

If you get an access token and pass it along, it will be used to sign and authorize the underlying requests to the sources providers. See the demos on the REST API endpoints above for examples.

Using the REST API

The endpoints above all implement the PoCo REST API. Request paths are of the form:

/poco/@me/GROUP_ID/[USER_ID]?startIndex=...&count=...&format=FORMAT&access_token=...

All query parameters are optional. FORMAT may be json (the default), xml, or atom, both of which return Atom. The rest of the path elements and query params are described above.

Errors are returned with the appropriate HTTP response code, e.g. 403 for Unauthorized, with details in the response body.

To use the REST API in an existing PoCo client, you'll need to hard-code exceptions for the domains you want to use e.g. facebook.com, and redirect HTTP requests to the corresponding endpoint above.

Using the library

See the example above for a quick start guide.

Clone or download this repo into a directory named portablecontacts_unofficial (note the underscore instead of dash). Each source works the same way. Import the module for the source you want to use, then instantiate its class by passing the HTTP handler object. The handler should have a request attribute for the current HTTP request.

The useful methods are get_contact() and get_current_user(), which returns the current authenticated user (if any). See the individual method docstrings for details. All return values are Python dicts of decoded Portable Contacts JSON objects.

Future work

The REST APIs are currently much more usable than the library. We need to make the library easier to use. Most of the hard work is already done; here's what remains.

  • Allow passing OAuth tokens as keyword args.
  • Expose the initial OAuth permission flow. The hard work is already done, we just need to let users trigger it programmatically.
  • Expose the format arg and let users request Atom output.

We'd also love to add more sites! Off the top of my head, Yahoo, Outlook.com, Apple's iCloud, Instagram, and Sina Weibo would be good candidates. If you're looking to get started, implementing a new site is a good place to start. It's pretty self contained and the existing sites are good examples to follow, but it's a decent amount of work, so you'll be familiar with the whole project by the end.

Finally, here are some Facebook tweaks we should implement to make sure we comply with their TOS:

Development

Pull requests are welcome! Feel free to ping me with any questions.

Most dependencies are included as git submodules. Be sure to run git submodule init after cloning this repo.

This Portable Contacts test client and validator is useful for manual testing.

You can run the unit tests with ./alltests.py. They depend on the App Engine SDK and mox, both of which you'll need to install yourself.

Note the app.yaml.* files, one for each App Engine app id. To work on or deploy a specific app id, symlink app.yaml to its app.yaml.xxx file. Likewise, if you add a new site, you'll need to add a corresponding app.yaml.xxx file.

To deploy:

rm -f app.yaml && ln -s app.yaml.twitter app.yaml && \
  ~/google_appengine/appcfg.py --oauth2 update . && \
rm -f app.yaml && ln -s app.yaml.facebook app.yaml && \
  ~/google_appengine/appcfg.py --oauth2 update .