The social web translator. Fetches and converts data between social networks, HTML and JSON with microformats2, ActivityStreams, Atom, JSON Feed, and more.
Granary is a library and REST API that fetches and converts between a wide variety of data sources and formats:
- Facebook, Flickr, GitHub, Instagram, Mastodon, and Twitter native APIs
- Instagram and Facebook scraped HTML
- ActivityStreams 1.0 and 2.0 (JSON)
- HTML and JSON with microformats2
- Atom, RSS 2.0, JSON Feed
- Plain XML
Free yourself from silo API chaff and expose the sweet social data foodstuff inside in standard formats and protocols!
Here's how to get started:
- Granary is available on PyPi. Install with
pip install granary
. - Click here for getting started docs.
- Click here for reference docs.
- The REST API and demo app are deployed at granary.io.
Versions 3.0 and above support App Engine's Python 3 runtimes, both Standard and Flexible. If you're on the Python 2 runtime, use version 2.2.
License: This project is placed in the public domain.
All dependencies are handled by pip and enumerated in requirements.txt. We recommend that you install with pip in a virtualenv
. (App Engine details.)
The library and REST API are both based on the OpenSocial Activity Streams service.
Let's start with an example. This code using the library:
from granary import twitter
...
tw = twitter.Twitter(ACCESS_TOKEN_KEY, ACCESS_TOKEN_SECRET)
tw.get_activities(group_id='@friends')
is equivalent to this HTTP GET
request:
https://granary.io/twitter/@me/@friends/@app/
?access_token_key=ACCESS_TOKEN_KEY&access_token_secret=ACCESS_TOKEN_SECRET
They return the authenticated user's Twitter stream, ie tweets from the people they follow. Here's the JSON output:
{
"itemsPerPage": 10,
"startIndex": 0,
"totalResults": 12,
"items": [{
"verb": "post",
"id": "tag:twitter.com,2013:374272979578150912",
"url": "http://twitter.com/evanpro/status/374272979578150912",
"content": "Getting stuff for barbecue tomorrow. No ribs left! Got some nice tenderloin though. (@ Metro Plus Famille Lemay) http://t.co/b2PLgiLJwP",
"actor": {
"username": "evanpro",
"displayName": "Evan Prodromou",
"description": "Prospector.",
"url": "http://twitter.com/evanpro",
},
"object": {
"tags": [{
"url": "http://4sq.com/1cw5vf6",
"startIndex": 113,
"length": 22,
"objectType": "article"
}, "..."],
},
}, "..."]
"..."
}
The request parameters are the same for both, all optional: USER_ID
is a source-specific id or @me
for the authenticated user. GROUP_ID
may be @all
, @friends
(currently identical to @all
), @self
, @search
, or @blocks
; APP_ID
is currently ignored; best practice is to use @app
as a placeholder.
Paging is supported via the startIndex
and count
parameters. They're self explanatory, and described in detail in the OpenSearch spec and OpenSocial spec.
When using the GROUP_ID
@search
(for platforms that support it — currently Twitter and Instagram), provide a search string via the q
parameter. The API is loosely based on the OpenSearch spec, the OpenSocial Core Container spec, and the OpenSocial Core Gadget spec.
Output data is JSON Activity Streams 1.0 objects wrapped in the OpenSocial envelope, which puts the activities in the top-level items
field as a list and adds the itemsPerPage
, totalCount
, etc. fields.
Most Facebook requests and all Twitter, Instagram, and Flickr 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, Flickr, Instagram, 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.
The endpoints above all serve the OpenSocial Activity Streams REST API. Request paths are of the form:
/USER_ID/GROUP_ID/APP_ID/ACTIVITY_ID?startIndex=...&count=...&format=FORMAT&access_token=...
All query parameters are optional. FORMAT
may be as1
(the default), as2
, atom
, html
, jsonfeed
, mf2-json
, rss
, or xml
(the default). atom
supports a boolean reader
query parameter for toggling rendering appropriate to feed readers, e.g. location is rendered in content when reader=true
(the default). 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.
By default, responses are cached and reused for 10m without re-fetching the source data. (Instagram responses are cached for 60m.) You can prevent this by adding the cache=false
query parameter to your request.
To use the REST API in an existing ActivityStreams 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.
Instagram is disabled in the REST API entirely, sadly, due to their aggressive rate limiting and blocking.
The web UI (granary.io) currently only fetches Facebook access tokens for users. If you want to use it to access a Facebook page, you'll need to get an access token manually with the Graph API Explorer (click on the Get To... drop-down) . Then, log into Facebook on granary.io and paste the page access token into the access_token
text box.
See the example above for a quick start guide.
Clone or download this repo into a directory named granary
(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_activities()
and get_actor()
, which returns the current authenticated user (if any). See the individual method docstrings for details. All return values are Python dicts of decoded ActivityStreams 1 JSON.
The microformats2.*_to_html()
functions are also useful for rendering ActivityStreams 1 objects as nicely formatted HTML.
Check out the oauth-dropins Troubleshooting/FAQ section. It's pretty comprehensive and applies to this project too. For searchability, here are a handful of error messages that have solutions there:
bash: ./bin/easy_install: ...bad interpreter: No such file or directory
ImportError: cannot import name certs
ImportError: cannot import name tweepy
File ".../site-packages/tweepy/auth.py", line 68, in _get_request_token
raise TweepError(e)
TweepError: must be _socket.socket, not socket
We'd love to add more sites! Off the top of my head, YouTube, Tumblr, WordPress.com, Sina Weibo, Qzone, and RenRen 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.
Pull requests are welcome! Feel free to ping me with any questions.
First, fork and clone this repo. Then, you'll need the Google Cloud SDK with the gcloud-appengine-python
and gcloud-appengine-python-extras
components. Once you have them, set up your environment by running these commands in the repo root directory:
gcloud config set project granary-demo
python3 -m venv local3
source local3/bin/activate
pip install -r requirements.txt
ln -s local3/lib/python3*/site-packages/oauth_dropins
Now, run the tests to check that everything is set up ok:
gcloud beta emulators datastore start --no-store-on-disk --consistency=1.0 --host-port=localhost:8089 < /dev/null >& /dev/null &
python3 -m unittest discover
kill %1
Finally, run this in the repo root directory to start the web app locally:
dev_appserver.py --log_level debug --enable_host_checking false \
--support_datastore_emulator --datastore_emulator_port=8089 \
--application=granary-demo app.yaml
Open localhost:8080 and you should see the granary home page!
If you want to work on oauth-dropins at the same time, install it in "source" mode with pip install -e <path to oauth-dropins repo>
. You'll also need to update the oauth_dropins
symlink, which is needed for serving static file assets in dev_appserver: ln -sf <path-to-oauth-dropins-repo>/oauth_dropins
.
To deploy to production:
gcloud -q beta app deploy --no-cache granary-demo *.yaml
The docs are built with Sphinx, including apidoc, autodoc, and napoleon. Configuration is in docs/conf.py
To build them, first install Sphinx with pip install sphinx
. (You may want to do this outside your virtualenv; if so, you'll need to reconfigure it to see system packages with virtualenv --system-site-packages local
.) Then, run docs/build.sh
.
This ActivityStreams validator is useful for manual testing.
Here's how to package, test, and ship a new release. (Note that this is largely duplicated in the oauth-dropins readme too.)
- Run the unit tests.
source local3/bin/activate.csh CLOUDSDK_CORE_PROJECT=granary-demo gcloud beta emulators datastore start --no-store-on-disk --consistency=1.0 --host-port=localhost:8089 < /dev/null >& /dev/null & sleep 5 python3 -m unittest discover kill %1 deactivate
- Bump the version number in
setup.py
anddocs/conf.py
.git grep
the old version number to make sure it only appears in the changelog. Change the current changelog entry inREADME.md
for this new version from unreleased to the current date. - Build the docs. If you added any new modules, add them to the appropriate file(s) in
docs/source/
. Then run./docs/build.sh
. Check that the generated HTML looks fine by openingdocs/_build/html/index.html
and looking around. git commit -am 'release vX.Y'
- Upload to test.pypi.org for testing.
python3 setup.py clean build sdist setenv ver X.Y source local3/bin/activate.csh twine upload -r pypitest dist/granary-$ver.tar.gz
- Install from test.pypi.org.
python3 -m venv local3 source local3/bin/activate.csh pip3 uninstall granary # make sure we force Pip to use the uploaded version pip3 install --upgrade pip pip3 install mf2py==1.1.2 pip3 install -i https://test.pypi.org/simple --extra-index-url https://pypi.org/simple granary==$ver deactivate
- Smoke test that the code trivially loads and runs.
Test code to paste into the interpreter:
source local3/bin/activate.csh python3 # run test code below deactivate
import json from granary import instagram instagram.__file__ # check that it's in the virtualenv i = instagram.Instagram() a = i.get_activities(user_id='snarfed', group_id='@self', scrape=True) print(json.dumps(a, indent=2)) from granary import atom print(atom.activities_to_atom(a, {})) from granary import github g = github.GitHub('XXX') # insert a GitHub personal OAuth access token a2 = g.get_activities() print(json.dumps(a2, indent=2))
- Tag the release in git. In the tag message editor, delete the generated comments at bottom, leave the first line blank (to omit the release "title" in github), put
### Notable changes
on the second line, then copy and paste this version's changelog contents below it.git tag -a v$ver --cleanup=verbatim git push git push --tags
- Click here to draft a new release on GitHub. Enter
vX.Y
in the Tag version box. Leave Release title empty. Copy### Notable changes
and the changelog contents into the description text box. - Upload to pypi.org!
twine upload dist/granary-$ver.tar.gz
- Build the docs on Read the Docs: first choose latest in the drop-down, then click Build Version.
- On the Versions page, check that the new version is active, If it's not, activate it in the Activate a Version section.
Apache Streams is a similar project that translates between storage systems and database as well as social schemas. It's a Java library, and its design is heavily structured. Here's the list of formats it supports. It's mainly used by People Pattern.
Gnip similarly converts social network data to ActivityStreams and supports many more source networks. Unfortunately, it's commercial, there's no free trial or self-serve signup, and plans start at $500.
DataSift looks like broadly the same thing, except they offer self-serve, pay as you go billing, and they use their own proprietary output format instead of ActivityStreams. They're also aimed more at data mining as opposed to individual user access.
Cliqset's FeedProxy used to do this kind of format translation, but unfortunately it and Cliqset died.
Facebook used to officially support ActivityStreams, but that's also dead.
There are a number of products that download your social network data, normalize it, and let you query and visualize it. SocialSafe is one, although the SSL certificate is currently out of date. ThinkUp was an open source product, but shuttered on 18 July 2016. There's also the lifelogging/lifestream aggregator vein of projects that pull data from multiple source sites. Storytlr is a good example. It doesn't include Facebook, or Instagram, but does include a number of smaller source sites. There are lots of others, e.g. the Lifestream WordPress plugin. Unfortunately, these are generally aimed at end users, not developers, and don't usually expose libraries or REST APIs.
On the open source side, there are many related projects. php-mf2-shim adds microformats2 to Facebook and Twitter's raw HTML. sockethub is a similar "polyglot" approach, but more focused on writing than reading.
- N/A
Breaking changes:
- Python 2 is no longer supported! Including the App Engine Standard Python 2 runtime. On the plus side, the Python 3 runtime is now supported! See this list of differences for more details.
Non-breaking changes:
- Migrate demo app and API to the App Engine Standard Python 3 runtime.
- Instagram:
- Scraping: fetch 50 likes instead of 24. (snarfed/bridgy#898)
- Scraping bug fix for
get_actor()
withuser_id
.
- Twitter:
- Add image alt text support to
get_activites()
etc (#183).
- Add image alt text support to
- RSS:
- Add
itunes:image
,itunes:author
, anditunes:category
. - Strip HTML from
title
element (#177). Background. - Always include author in items (#177).
- Bug fix: extract feed image from
hfeed
correctly. - Bug fix: don't crash on
article
ormention
tags in items with enclosures.
- Add
- Atom:
- Bug fix: extract feed image from
hfeed
correctly.
- Bug fix: extract feed image from
- REST API:
- Add HTTP
HEAD
support. - Add support for URL fragments with
input=html
. If a fragment is provided, only that specific element is extracted and converted. (#185)
- Add HTTP
- GitHub:
- Publish: preserve
<code>
tags instead of converting them to `s so that GitHub renders HTML entities like>
inside them instead of leaving them escaped. Background.
- Publish: preserve
- JSON Feed:
- Handle malformed attachments better.
- microformats2:
- Don't crash on string
context
fields. html_to_activities()
: limit toh-entry
,h-event
, andh-cite
items (#192).
- Don't crash on string
- The
cache
kwarg toSource.original_post_discovery()
now has no effect.webutil.util.follow_redirects()
has its own built in caching now. - Added Meetup.com support for publishing RSVPs.
- Add Mastodon support!
- Add Python 3.7 support, and improve overall Python 3 compatibility.
- Update a number of dependencies.
- Switch from Python's built in
json
module toujson
to speed up JSON parsing and encoding. - Add
duration
andsize
support to ActivityStreams 1 and 2, RSS, and microformats2 HTML and JSON. microformats2 support is still emerging for both. Both integer seconds and ISO 8601 string durations are supported forduration
. Integer bytes is used forsize
everywhere. microformats2 HTML also includes human-readable strings, eg5.1 MB
. (#169) - Twitter:
[preview]_create()
: detect attempts to upload images over 5MB and return an error.
- Facebook:
- Add
get_activities(scrape=True)
for scraping HTML from m.facebook.com. Requiresc_user
andxs
cookies from a logged in session (snarfed/bridgy#886). - Upgrade Graph API version from 2.10 to 4.0.
- Add
- Atom:
- Bug fix for de-duping images in attachments.
- RSS:
- Wrap all
<description>
element contents inCDATA
sections. - Render images in
<description>
with HTML<img>
tags (#175). from_activities()
bug fix: don't crash when converting multiple attachments to enclosures in a single item. (RSS only supports one enclosure per item, so we now only include the first, and log a warning if the activity has more.)
- Wrap all
- Convert AS2
Mention
tags to AS1objectType
mention
(non-standard) and vice versa (snarfed/bridgy-fed#46). - Twitter:
- Bug fix for large block list fetches that get rate limited after a few successful requests.
- Handle HTTP 403 + error code 200 when fetching retweets for a protected or otherwise unavailable tweet (bridgy#688).
- Demote @-mentions from person-tags to mentions. Specifically, this means they'll no longer get rendered with
u-category
mf2.
- Instagram:
- Disabled in the REST API entirely due to Instagram's aggressive rate limiting and blocking (bridgy#655).
- Update scraping to handle replies in new
edge_media_to_parent_comment
field (#164). - Use cookie for all scraping HTTP requests, not just for likes.
- microformats2:
- Revise whitespace handling; use
white-space: pre
CSS in HTML output.
- Revise whitespace handling; use
- Facebook:
- Bug fix: don't interpret
photo.php
as username in post URLs.
- Bug fix: don't interpret
- Atom:
- Switch from
white-space: pre
CSS back to converting newlines to<br>
s because some feed readers (eg NewsBlur) follow it too strictly and don't even line wrap.
- Switch from
- RSS:
- Default title to ellipsized content.
Breaking change: drop Google+ since it shuts down in March. Notably, this removes the googleplus
module.
- Add RSS 2.0 output! (#124)
- All silos:
- Switch users' primary URLs from web site to silo profile (#158).
- GitHub:
- Don't enclose bare URLs in
<
/>
(snarfed/bridgy#850).
- Don't enclose bare URLs in
- Atom:
- Bug fix for actors and attachments with multiple image URLs.
- Bug fix for attachment author objects with no properties.
- Google+:
- Drop from web UI and REST API since consumer Google+ is shutting down entirely (more).
- Switch from deprecated global API endpoint to G+ endpoint. Background in snarfed/bridgy#846, Google blog post and docs.
- Instagram:
- Fix individual photo/video link urls for multi-photo/video posts.
- Handle user-provided alt text (#159).
- Twitter:
- Update max video upload size from 5MB to 512MB (#162).
/url
: Return HTTP 400 when fetching the user's URL results in an infinite redirect.
Add delete()
. Currently includes Twitter and Flickr support.
- Instagram:
- Make extra HTTP fetch (with cookie) to get individual likes (snarfed/bridgy#840).
- Update scraping logic to handle feed HTML changes.
- Link @-mentions in comments as well as photo/video captions.
- GitHub:
create
/preview_create
bug fixes for issues and comments on private repos.- Handle HTTP 410 Gone responses from REST API, eg when a repo has been deleted or issues for the repo disabled.
- Twitter:
- Add
delete()
andpreview_delete()
for deleting tweets.
- Add
- Flickr:
- Add
delete()
andpreview_delete()
for deleting photos.
- Add
- microformats2:
- Atom:
- Encode
&
s in author URL and email address too. (Thanks sebsued!)
- Encode
- AS2:
- Add
Follow
support.
- Add
- Twitter:
- Support ISO 8601 formatted created_at timestamps, which the archive download uses, as well as RFC 2822 from the API.
create()
andpreview_create()
: support RSVPs. Tweet them as normal tweets with the RSVP content. (snarfed/bridgy#818)create()
andpreview_create()
: support alt text for images, via AS1displayName
. (snarfed/bridgy#756).
- Instagram:
- Add global rate limiting lock for scraping. If a scraping HTTP request gets a 429 or 503 response, we refuse to make more requests for 5m, and instead short circuit and return the same error. This can be overridden with a new
ignore_rate_limit
kwarg toget_activities()
.
- Add global rate limiting lock for scraping. If a scraping HTTP request gets a 429 or 503 response, we refuse to make more requests for 5m, and instead short circuit and return the same error. This can be overridden with a new
- GitHub:
- Add
tag
support tocreate
/preview_create
to add label(s) to existing issues (snarfed/bridgy#811). - Escape HTML characters (
<
,>
, and&
) in content increate()
andpreview_create()
(snarfed/bridgy#810). get_activities()
andget_comment()
now returnValueError
instead ofAssertionError
on malformedactivity_id
andcomment_id
args, respectively.get_activities()
bug fix for issues/PRs with no body text.- Switch from GraphQL to REST API for creating comments and reactions, since GraphQL hits authorization errors on many org repos. (snarfed/bridgy#824)
- Improve GraphQL support for comments and users.
- Add
- Atom:
- Shorten and ellipsize feed title when necessary (#144).
- microformats2:
- Upgrade mf2py to improve a few things like implied p-name detection and whitespace handling (#142, fixes #145, snarfed/bridgy#756, snarfed/bridgy#828).
- Support
alt
attribute in<img>
tags (snarfed/bridgy#756).
- Add Python 3 support! Granary now requires either Python 2.7+ or Python 3.3+.
- Instagram:
- Fix scraping profile pages.
- Twitter:
- Update character counting to handle Twitter change that now auto-links all ccTLDs. Background.
- GitHub:
- Bug fix for
get_activities()
with deleted issues and repos.
- Bug fix for
- microformats2:
object_to_json()
: convert tags to simple strings in thecategory
property, not full nested objects likeh-card
s (#141).- Special case GitHub issues that are in-reply-to a repo or its
/issues
URL to be objectTypeissue
. - Render simple string categories in HTML output.
This release is intentionally small and limited in scope to contain any impact of the Python 3 migration. It should be a noop for existing Python 2 users, and we've tested thoroughly, but I'm sure there are still bugs. Please file issues if you notice anything broken!
- Add GitHub!
- Twitter:
- Prefer MP4 and other video/... content types to HLS (.m3u8) etc. Background.
- Prefer HTTPS URLs for media images.
get_activities()
: Support @-prefixed usernames inuser_id
.
- Facebook:
- Support new recurring aka multi-instance events.
create()
andpreview_create()
now only support RSVPs to individual instances of multi-instance events, to match the Facebook API itself. - Try harder to find original (full) sized photo URLs, specifically
_o.jpg
files instead of_s.jpg
. create()
bug fix for photo and image URLs with unicode characters.- Fixed bug where
get_activities(user_id=...)
included the authenticated user's own recent photos, albums, and news publishes.
- Support new recurring aka multi-instance events.
- Instagram:
- Extract more user (
author
) data from scraped profile pages. - Fix home page feed scraping.
- Extract more user (
- microformats2, Atom:
- Add enclosures for image attachments.
- Bug fixes for rendering image, video, and audio attachments inside shares and attachments. De-dupe images.
- microformats2:
- Handle simple string-only author properties.
- Add
fetch_mf2
kwarg tojson_to_object()
for fetching additional pages over HTTP to determine authorship. - Generate explicit blank
p-name
in HTML to prevent old flawed implied p-name handling (#131). - Fix
share
verb handling inactivity_to_json()
andactivities_to_html()
(#134). - Remember which content contains HTML, preserve newlines in it, and don't translate those newlines to
<br>
s (#130).
- Atom:
- Fix timezone bugs in
updated
andpublished
.
- Fix timezone bugs in
- JSON Feed:
- Omit title from items if it's the same as the content. (Often caused by microformats2's implied
p-name
logic.)
- Omit title from items if it's the same as the content. (Often caused by microformats2's implied
- Moved web site and REST API to granary.io! granary-demo.appspot.com now 301 redirects.
- Twitter:
- Update the publish character limit to 280. Background.
- Fix a bug in preview_create that auto-linked @-mentions inside URLs, e.g. Medium posts.
- Support videos and animated GIFs in
get_activities()
etc.
- Instagram:
- Add cookie query param to REST API to allow scraping that logged in user's feed.
- HTML (including Atom content):
- Render image, video, and audio attachments more often and consistently.
- Include microformats2
u-photo
,u-video
, andu-audio
classes more often and consistently.
- Atom:
- Add
atom_to_activities()
for converting full feed documents. - Add to REST API and web UI.
- Include source URL in
rel=alternate
link as well as actor/author URL (#151).
- Add
- JSON Feed:
- Fix bug that omitted title in some cases (#122).
- Add ActivityStreams 2.0! New
as2
module includesto_as1()
andfrom_as1()
functions. Currently supported: articles, notes, replies, likes, reposts, events, RSVPs, tags, attachments. - Atom:
- Add new
atom_to_activity()
function for converting Atom to AS1. - Add email field to author, if provided.
- Add new
- JSON Feed:
- Raise ValueError on bad (non-dict) input.
- REST API:
- Add
as2
value forformat
andinput
. Revise existing ActivityStreams and microformats2 value names toas1
,as1-xml
, andmf2-json
. Old valuesactivitystreams
,json
,json-mf2
, andxml
are still accepted, but deprecated.
- Add
- Add JSON Feed support to both library and REST API.
- Twitter:
- Add
get_blocklist()
. - Bug fix for creating replies, favorites, or retweets of video URLs, e.g. https://twitter.com/name/status/123/video/1 .
- Bug fix for parsing favorites HTML to handle a small change on Twitter's side.
post_id()
now validates ids more strictly before returning them.
- Add
- Facebook:
- Instagram:
- Update scraping to handle new home page (ie news feed) JSON schema, which changed sometime around 2017-02-27. (Profile pages and individual photo/video permalinks still haven't changed yet.)
- microformats2:
- Add
u-featured
to ActivityStreamsimage
. - Improve
h-event
support. - Minor whitespace change (added
) when rendering locations as HTML.
post_id()
now validates ids more strictly before returning them.- Fix bugs in converting latitude and longitude between ActivityStreams and mf2.
- Add
- Google+:
- Update HTML scraping to handle changed serialized JSON data format.
- Atom:
- Add new
activity_to_atom()
function that renders a single top-level<entry>
instead of<feed>
. - Add new
reader
query param for toggling rendering decisions that are specific to feed readers. Right now, just affects location: it's rendered in the content whenreader=true
(the default), omitted whenreader=false
. - Include author name when rendering attached articles and notes (e.g. quote tweets).
- Only include AS
activity:object-type
andactivity:verb
elements when they have values. - Render AS image and mf2 u-photo if they're not already in content.
- Render
thr:in-reply-to
fromobject.inReplyTo
as well asactivity.context.inReplyTo
.
- Add new
- REST API:
- Fix bugs in html => json-mf2 and html => html conversions.
- Upgrade brevity to 0.2.14 for a couple bug fixes.
- microformats2:
- Interpret h-cite and u-quotation-of (experimental) as attachments, e.g. for quote tweets.
- Convert audio and video properties to AS attachments.
- Twitter:
- Linkify @-mentions and hashtags in
preview_create()
. - Support creating quote tweets from attachments with Twitter URLs.
- When converting quote tweets to AS, strip quoted tweet URL from end of text.
- Raise ValueError when
get_activities()
is passedgroup_id='@search'
but notsearch_query
.
- Linkify @-mentions and hashtags in
- Instagram:
- Improve HTML scraping error handling.
- Support multi-photo/video posts.
- Facebook:
- Disable creating "interested" RSVPs, since Facebook's API doesn't allow it.
- Atom:
- Support media enclosures for audio and video attachments.
- Source.get_activities(): start raising ValueError on bad argument values, notably invalid Facebook and Twitter ids and Instagram search queries.
- Fix rendering and linkifying content with Unicode high code points (ie above the 16-bit Basic Multilingual Plane), including some emoji, on "narrow" builds of Python 2 with
--enable-unicode=ucs2
, which is the default on Mac OS X, Windows, and older *nix.
- Twitter:
- Handle new "extended" tweets with hidden reply-to @-mentions and trailing URLs for media, quote tweets, etc. Background: https://dev.twitter.com/overview/api/upcoming-changes-to-tweets
- Bug fix: ensure like.author.displayName is a plain unicode string so that it can be pickled normally, e.g. by App Engine's memcache.
- Bug fix: handle names with emoji correctly in favorites_html_to_likes().
- Bug fix: handle search queries with unicode characters.
- Atom:
- Render full original quoted tweet in retweets of quote tweets.
- microformats2 HTML:
- Optionally follow and fetch rel="author" links.
- Improve mapping between microformats2 and ActivityStreams 'photo' types. (mf2 'photo' type is a note or article with a photo, but AS 'photo' type is a photo. So, map mf2 photos to underlying type without photo.)
- Support location properties beyond h-card, e.g. h-adr, h-geo, u-geo, and even when properties like latitude and longitude appear at the top level.
- Error handling: return HTTP 502 for non-JSON API responses, 504 for connection failures.
- REST API:
- Support tag URI for user id, app id, and activity id.
- Twitter:
- Better error message when uploading a photo with an unsupported type.
- Only include original quote tweets, not retweets of them.
- Skip fetching retweets for protected accounts since the API call always 403s.
- Flickr:
- Better username detection. Flickr's API is very inconsistent about username vs real name vs path alias. This specifically detects when a user name is probably actually a real name because it has a space.
- Uploading: detect and handle App Engine's 10MB HTTP request limit.
- Bug fix in create: handle unicode characters in photo/video description, hashtags, and comment text.
- Atom:
- Bug fix: escape &s in attachments' text (e.g. quote tweets).
- Bug fix: handle multiply valued 'object' fields in ActivityStreams 1 activities.
- GitHub:
- Switch creating comments and reactions from GraphQL to REST API (bridgy#824.
- Bump oauth-dropins requirement to 1.4.
- REST API:
- Cache silo requests for 5m by default, 60m for Instagram because they aggressively blocking scraping. You can skip the cache with the new cache=false query param.
- Facebook:
- Upgrade from API v2.2 to v2.6. https://developers.facebook.com/docs/apps/changelog
- Add reaction support.
- De-dupe event RSVPs by user.
- Twitter:
- Switch create() to use brevity for counting characters. https://github.com/kylewm/brevity
- Fix bug in create() that occasionally incorrectly escaped ., +, and - characters.
- Fix text rendering bug when there are multipl photos/videos.
- When replying to yourself, don't add a self @-mention.
- Instagram:
- Fix bugs in scraping.
- Upgrade to requests 2.10.0 and requests-toolbelt 0.60, which support App Engine.
- Update oauth-dropins dependency to >=1.3.
- Support posting videos! Currently in Facebook, Flickr, and Twitter.
- Instagram:
- Add support for scraping, since they're locking down their API and requiring manual approval.
- Linkify @-mentions in photo captions.
- Facebook:
- Fetch Open Graph stories aka
news.publish
actions. - Many bug fixes for photo posts: better privacy detection, fix bug that attached comments to wrong posts.
- Fetch Open Graph stories aka
- Twitter:
- Handle all photos/videos attached to a tweet, not just the first.
- Stop fetching replies to @-mentions.
- Atom:
- Render attachments.
- Add
xml:base
.
- microformats2:
- Load and convert h-card.
- Implement full post type discovery algorithm, using mf2util. https://indiewebcamp.com/post-type-discovery
- Drop support for h-as-* classes, both incoming and outgoing. They're deprecated in favor of post type discovery.
- Drop old deprecated
u-like
andu-repost
properties.
- Misc bug fixes.
- Set up Coveralls.
- Improve original post discovery algorithm. (bridgy #51)
- Flickr tweaks. (bridgy #466)
- Add mf2, activitystreams, atom, and search to interactive UI. (#31, #29)
- Improved post type discovery (using mf2util).
- Extract user web site links from all fields in profile (e.g. description/bio).
- Add fabricated fragments to comment/like permalinks (e.g. #liked-by-user123) so that object urls are always unique (multiple silos).
- Improve formatting/whitespace support in create/preview (multiple silos).
- Google+:
- Add search.
- Facebook:
- Fetch more things in get_activities: photos, events, RSVPs.
- Support person tags in create/preview.
- Prevent facebook from automatically consolidating photo posts by uploading photos to "Timeline Photos" album.
- Include title in create/preview.
- Improve object id parsing/resolving.
- Improve tag handling.
- Bug fix for fetching nested comments.
- Misc improvements, API error/flakiness handling.
- Flickr:
- Create/preview support for photos, comments, favorites, tags, person tags, location.
- Twitter:
- Create/preview support for location, multiple photos.
- Fetch quote tweets.
- Fetching user mentions improvements, bug fixes.
- Fix embeds.
- Misc AS conversion improvements.
- microformats2:
- Improve like and repost rendering.
- Misc bug fixes.
- Set up CircleCI.
- Add Flickr.
- Facebook:
- Fetch multiple id formats, e.g. with and without USERID_ prefix.
- Support threaded comments.
- Switch from /posts API endpoint to /feed.
- Google+:
- Support converting plus.google.com HTML to ActivityStreams.
- Instagram:
- Support location.
- Improve original post discovery algorithm.
- New logo.
- Bug fix for atom template rendering.
- Facebook, Instagram: support access_token parameter.
- Initial PyPi release.