The social web translator. Fetches and converts data between social networks, HTML and JSON with microformats2, ActivityStreams/ActivityPub, Atom, JSON Feed, and more.
Granary is a library and REST API that fetches and converts between a wide variety of social 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, including ActivityPub
- HTML and JSON with microformats2
- Atom, RSS 2.0, JSON Feed
- Plain XML
- Bluesky/AT Protocol
- Nostr, with many NIPs
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. - Getting started docs.
- Reference docs.
- REST API and demo app at granary.io.
- Source code on GitHub.
License: This project is placed in the public domain. You may also use it under the CC0 License.
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.
Include the shares=false query parameter to omit shares, eg Twitter retweets, from the results.
To use the REST API in an existing ActivityStreams/ActivityPub 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.
Facebook and Instagram are disabled in the REST API entirely, sadly.
See the example above for a quick start guide.
Clone or download this repo into a directory named granary. 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 full reference docs 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.
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 in #indieweb-dev with any questions.
First, fork and clone this repo. Then, install the Google Cloud SDK and run gcloud components install cloud-firestore-emulator to install the Firestore emulator. 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 local
source local/bin/activate
pip install -r requirements.txt
# needed to serve static files locally
ln -s local/lib/python3*/site-packages/oauth_dropins/static oauth_dropins_staticNow, run the tests to check that everything is set up ok:
gcloud emulators firestore start --host-port=:8089 --database-mode=datastore-mode < /dev/null >& /dev/null &
python3 -m unittest discoverFinally, run the web app locally with flask run:
GAE_ENV=localdev FLASK_ENV=development flask run -p 8080Open 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 editable mode with pip install -e <path to oauth-dropins repo>. You'll also need to update the oauth_dropins_static symlink, which is needed for serving static file handlers locally: ln -sf <path-to-oauth-dropins-repo>/oauth_dropins/static oauth_dropins_static.
To deploy to production:
gcloud -q beta app deploy --no-cache granary-demo *.yamlThe 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.
Here's how to package, test, and ship a new release. (Note that this is largely duplicated in the oauth-dropins readme too.)
- Pull from remote to make sure we're at head.
git checkout main git pull
- Run the unit tests.
source local/bin/activate.csh CLOUDSDK_CORE_PROJECT=granary-demo gcloud emulators firestore start --host-port=:8089 --database-mode=datastore-mode < /dev/null >& /dev/null & sleep 5 python -m unittest discover kill %1
- Bump the version number in
setup.pyanddocs/conf.py.git grepthe old version number to make sure it only appears in the changelog. Change the current changelog entry inREADME.mdfor this new version from unreleased to the current date. - Bump the
oauth-dropinsversion specifier insetup.pyto the most recent version. - 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.htmland looking around. git commit -am 'release vX.Y'- Upload to test.pypi.org for testing.
python setup.py clean build sdist setenv ver X.Y twine upload -r pypitest dist/granary-$ver.tar.gz - Install from test.pypi.org.
cd /tmp python -m venv local source local/bin/activate.csh pip uninstall granary # make sure we force Pip to use the uploaded version pip install --upgrade pip pip install mf2py==1.1.2 pip install -i https://test.pypi.org/simple --extra-index-url https://pypi.org/simple granary==$ver
- Smoke test that the code trivially loads and runs.
Test code to paste into the interpreter:
python # run test code belowimport json from granary import github github.__file__ # check that it's in the virtualenv g = github.GitHub('XXX') # insert a GitHub personal OAuth access token a = g.get_activities() print(json.dumps(a, indent=2)) from granary import atom print(atom.activities_to_atom(a, {}))
- 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 changeson 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.Yin the Tag version box. Leave Release title empty. Copy### Notable changesand 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.
Breaking changes:
nostr:- Switch default AS1 actor and object ids from bech32-encoded to hex. This avoids confusion when the same id is bech32-encoded with a different prefix, eg
neventvsnote. The previous behavior, bech32-encoded ids, may still be generated by passingid_format='bech32'tonostr.to_as1.
- Switch default AS1 actor and object ids from bech32-encoded to hex. This avoids confusion when the same id is bech32-encoded with a different prefix, eg
pixelfed:- Switch AS1 actor and object ids from
tag:URIs to the real ActivityPub ids
- Switch AS1 actor and object ids from
Non-breaking changes:
- Switch from pillow to pymediainfo for extracting image metadata in
Bluesky.upload_media. as1:targets: exclude hashtags intags.
bluesky:- Support Web Monetization. Convert the AS2
monetizationfield in actors to/fromcommunity.lexicon.payments.webMonetizationrecords.to_as1only returns a single-element dict with keymonetization, not a real AS1 object. to_as1: add newclientkwarg, paralleling the existing one infrom_as1.
- Support Web Monetization. Convert the AS2
nostr:- Add support for
d(id) tags with kind 30023 (article) events. from_as1:- Bug fix for when
inReplyTois an object with anauthorfield. - Support
objectType: comment.
- Bug fix for when
to_as1:- Add new
id_formatkwarg for choosing between hex and bech32-encoded ids. - Add new
nostr_uri_idsboolean kwarg for whether to prefix ids withnostr:.
- Add new
user_url: expectnpubas input, notnprofile.
- Add support for
rss:to_as1: bug fix for multiple categories.
Breaking changes:
mastodon:- Switch AS1 actor and object ids from
tag:URIs to the real ActivityPub ids.
- Switch AS1 actor and object ids from
Non-breaking changes:
as1:prefix_urls: handle string values.is_public: returnFalsefor public CRUD activities on non-public objects.is_dm/recipient_if_dm: allow DMs with recipient inccinstead ofto; evidently NeoDB sends DMs like this.targets: add quoted posts, ie attachments withobjectType: note.- Add new
quoted_posts,mentionsfunctions.
as2:- Add support for pinned posts via the
featuredcollection. - Handle
repliesas a collection in bothfrom_as1andto_as1. from_as1:- Add
rel="tag"to hashtag HTML links andclass="h-card"to mentions incontentto prevent Mastodon from generating link previews for them.
- Add
to_as1:- Handle bare string
attachmentvalues. - Handle multiply-valued
type.
- Handle bare string
is_server_actor: returnFalsefor id URLs with query parameters.- Add new
URL_REconstant.
- Add support for pinned posts via the
bluesky:- Add
pds_url,**requests_kwargskwargs toBlueskyconstructor. - Convert
followersCount/followsCountinapp.bsky.actor.defs#profileViewDetailedto the non-standardfollowersandfollowingAS1 collections (borrowed from ActivityPub). - Convert pinned posts (just one per actor) between Bluesky's
pinnedPostfield and the fediverse'sfeaturedcollection. - Switch
getFollows/getFollowerscalls from PDS to AppView. (These requests to Bluesky PDSes were timing out connection from Google Cloud IPs as of 2025-05-08.) from_as1:- If
contentis in a language that doesn't delimit words by spaces, truncate between any characters (snarfed/bridgy-fed#1625). - Bug fix: handle bare string
image. - Bug fix for when last line of content is a bsky.app URL that's not a post (bridgy-fed#2064).
- If
to_as1:- Bug fix, handle facets with bad indices (in the middle of Unicode code points) interacting with HTML characters.
- Better error handling when embed record
$typeis invalid.
web_url_to_at_uriandBSKY_APP_URL_RE: tighten validation, check authority and rkey for allowed characters.
- Add
mastodon:- Convert
followers_count/following_countin Mastodon accounts to the non-standardfollowersandfollowingAS1 collections (borrowed from ActivityPub). - Add
**requests_kwargstoBlueskyconstructor.
- Convert
microformats2:from_as1: for quote posts (note attachments), populate theiridintourl, notuid(bridgy-fed#2045).to_as1:- Bug fix: handle
u-bookmark-of h-cite(#918). - Only populate
u-urlintourlif it's a valid URL.
- Bug fix: handle
nostr:- Add new
nip05_to_npubfunction to resolve NIP-05 identifiers. - Add new
bech32_decode,bech32_encode,bech32_prefix_for,pubkey_from_privkey,uri_forfunctions. - Add new
verifyfunction to verify event signatures. - Add NIP-24 support for additional tags and fields in profiles.
- Add NIP-92/94 support for
imetatags for images, video, audio. from_as1:- Add optional new
privkeykwarg to sign output events and populatepubkeywith. - Add optional new
from_protocolkwarg for setting NIP-48proxytags in output events. - When converting
created_attopublished, include UTC timezone. - When converting actors to kind 0 Nostr events, remove unset fields from content instead of setting them to null, and only use
_NIP-05 username with full domains. - Don't escape Unicode characters (background).
- Convert HTML
contentto Markdown plain text.
- Add optional new
Nostr:- Add new
user_urlmethod. - Add optional new
privkeykwarg to constructor to sign events with; removepubkeykwarg. createnow signs activities before sending to relays. Now requires theprivkeymember attribute to be set.query:- Verify signatures; discard events with invalid signatures.
- Add NIP-42 support; reply to
AUTHchallenges with signatures from the storedprivkey.
- Add new
- Add new
rss:to_as1: handle UNIX timestampdcterms:modifiedvalues without overflowing.
as2:- Add new
set_contentfunction to help keepcontentandcontentMapin sync. to_as1: support integer secondsduration, which is non-standard but sent by some AP implementations, eg Funkwhale.link_tags: addclass="hashtag"for hashtag (Tag,Hashtag) tags (bridgy-fed/#1634).
- Add new
bluesky:- Translate Bluesky
app.bsky.feed.post#tagsto/from AS1tags(snarfed/bridgy-fed#1394). - Add
authkwarg toBlueskyconstructor to pass through as custom auth object torequests.get/post. from_as1:- Bug fix for generating external embeds: convert HTML
content/summaryto plain textdescription(bridgy-fed#1615). - Don't add
app.bsky.feed.post#tagsthat are overmaxGraphemes(64). - Add new
raise_kwarg to raiseValueErrorif a required object (eg the target of a like or repost) can't be fetched via ATProto. - Bug fix: ignore
inReplyTofor DMs. - Don't convert HTML links in
contentwith bad URLs to#linkfacets.
- Bug fix for generating external embeds: convert HTML
to_as1:- Bug fix: HTML-escape
<and>characters, while preserving facet indices, so that they don't disappear (snarfed/bridgy-fed#1144).
- Bug fix: HTML-escape
preview/create:- Add support for follows.
to_external_embed: bug fix: handle compositeurlfield.
- Translate Bluesky
mastodon:preview/create:- Add support for follows.
nostr:- Add new
Nostr.deletemethod. - Add new
signfunction.
- Add new
source:Source.postprocess_object: relax mention text matching withmentions=True, ignore server part of webfinger addresses.- Add new
get_followsandget_followersmethods, implement in Mastodon and Bluesky.
Breaking changes:
as2:from_as1: InLinkobjects (includingTags andMentions), converturltohref. Before this, we left it asurl, which was incorrect AS2.
Non-breaking changes:
Standardize function and method names in all modules to to_as1, from_as, etc. Old method names are now deprecated but won't be removed until at least v9.0, if not later.
as1:- Add new
is_dm,recipient_if_dm,get_id, andis_audiencefunctions.
- Add new
as2:- Add
sensitive,indexable, anddiscoverablesupport. - Add new
is_server_actorfunction (FEP-d556, discussion). from_as1:- Always convert images to objects with
type: Image, never to bare string URLs (bridgy-fed#/1000). - Bug fixes for converting links to facets when the link text is the link URL.
- Always convert images to objects with
to_as1:- Handle other types of tags better, eg non-standard
Hashtagand innertagfield for name. - Bug fix for videos,
mimeTypegoes in outer object, not instream. - Bug fix for
to/ccwith mixed dict and string elements.
- Handle other types of tags better, eg non-standard
link_tags: addclass="mention"forMentiontags (bridgy-fed/#887).
- Add
atom:atom_to_activity/ies: Get URL fromlinkfor activities as well as objects. (Thanks @imax9000!)
bluesky:- Translate Bluesky
app.bsky.feed.post#langsto/from AS1contentMap(which isn't officially part of AS1; we steal it from AS2). - Translate AS2
sensitiveon posts to Blueskygraphic-mediaself label, and many Bluesky self labels back tosensitivewith content warning(s) insummary. - Translate AS1/2 DMs to/from Bluesky chats.
- Translate video embeds in posts.
create/previewCreate:- If
inReplyToisn't a Bluesky URL or AT URI, returnCreationResultinstead of raisingValueError.
- If
from_as1:- Convert
articles to external embeds with no post text. - Add new
as_embedboolean kwarg to do the same thing for any object. - When truncating and adding a link to the original post, use
idifurlis not available (snarfed/bridgy-fed#1155). - If the input object has
inReplyToorobjectortargetwith no recognizable ATProto or Bluesky object, raiseValueError. - Omit images that aren't in
blobs. - Bug fix for quote posts with text content that's longer than Bluesky's limit (snarfed/bridgy-fed#1197).
- When a
flaghas multiple objects, use the first one that's an ATProto record. - Handle URLs more carefully, don't add link facets with invalid
uris. - Populate
blobsinto external embedthumbs. - Parse image blobs and add
aspectRatioto image record. - Bug fix: handle HTML links with
titleincontentcorrectly. - Bug fix: handle attachments with no
idorurl.
- Convert
to_as1:- Extract links from
app.bsky.actor.profile#descriptionand#summaryintourl/urlsfields. - Bug fix: first URL (singular) goes in
url, list of URLs goes inurls. - Bug fix: handle hashtags with regexp special characters.
- Support string and bytes CIDs in blob
refs as well asCIDinstances. - Link hashtags to bsky.app hashtag search pages (bridgy-fed#1634).
- Extract links from
Bluesky.get_activities: skip unknown record types instead of raisingValueError.
- Translate Bluesky
microformats2:object_to_json: Improve handling of items with multiple types by removinginReplyTofrom likes, shares, etc (snarfed/bridgy-fed#941).to_as1: don't crash on integer UNIX timestamps inpublishedandupdated.
rss:- Support image enclosures, both directions.
from_as1:- Bug fix: remove use of default
authorvalue'-'since RSS spec requires author values to include valid email addresses.
- Bug fix: remove use of default
source:Source.postprocess_object: add newfirst_link_to_attachmentboolean kwarg to fetch and generate a previewattachmentfor the first link in the HTMLcontent, if any.
Breaking changes:
jsonfeed:jsonfeed_to_activities: return AS1 objects, not activities.
Non-breaking changes:
as1:activity_changed: adddisplayName,summaryfields.is_public: returnFalseif the object/activity containstothat's empty or has only unknown aliases.
as2:- Add support for the
Application,Block,Flag, andLinktypes. - Generalize actor logic in
to/from_as1across all actor types, not justPerson. - Add new
link_tagsfunction.
- Add support for the
atom:activities_to_atom: handle image attachments withouturlfield.
bluesky:to_as1:- Add support for:
app.bsky.embed.recordapp.bsky.embed.recordWithMediaapp.bsky.feed.defs#notFoundPostapp.bsky.feed.generatorapp.bsky.graph.blockapp.bsky.graph.listapp.bsky.graph.listitemcom.atproto.admin.defs#repoRefcom.atproto.moderation.createReport#inputcom.atproto.repo.strongRef
- Add hashtag facet support.
- Convert blobs in embeds to
getBlobimage URLs. app.bsky.actor.profile: add HTML links for URLs insummary(snarfed/bridgy-fed#1065).- Escape HTML characters (
<,>,&) inapp.bsky.actor.profiledescriptionfield. - Bug fix for
create/updateactivities with bare stringobject.
- Add support for:
from_as1:- Add hashtag, mention, block, and flag support. Interpret
tagswith missingobjectTypeas hashtags. - Guess missing indices in facets based on content text. Otherwise, if we still don't know a facet's indices, discard it.
- Extract HTML links ( tags) from HTML content and convert to link facets (snarfed/bridgy-fed#976).
- If an output string value is longer than its
maxGraphemesormaxLengthin its lexicon, truncate it with anβ¦ellipsis character at the end in order to fit. If this happens to post text, include a link embed pointing to the original post. - If the object has a video, include an external embed pointing to the original post and mark it as
[Video](snarfed/bridgy-fed#1078). - If the object has images, add the original post link to the end of the text, since Bluesky doesn't support both image and external embeds in the same post (bluesky-social/atproto#2575, snarfed/bridgy-fed#1106).
- If a
notehassummary- often used for content warnings in the fediverse - add it tocontentas a prefix instead of overridingcontent(snarfed/bridgy-fed#1001). - Populate
reply.rootproperly in reply posts (snarfed/bridgy#1696). - Add new
original_fields_prefixkwarg to store original data in custom (off-Lexicon)*OriginalDescriptionand*OriginalUrlfields inapp.bsky.actor.profileand*OriginalTextand*OriginalUrlfields inapp.bsky.feed.post(snarfed/bridgy-fed#1092). - Support
lexrpc.Clientas well asBlueskyforclientkwarg.
- Add hashtag, mention, block, and flag support. Interpret
from_as1_to_strong_ref:- Add
valueboolean kwarg. - Change
clientkwarg fromBlueskytolexrpc.Client.
- Add
microformats2:- Generalize actor logic across all actor types, not just
person. json_to_object:- Strip leading
#prefix (if present) from hashtagu-categorys. - Bug fix for when
nameproperty is an object, eg anh-card.
- Strip leading
object_to_json:- Convert both
idandurlinsideinReplyTotoin-reply-to.
- Convert both
- Generalize actor logic across all actor types, not just
nostr:- Handle connection closing while sending initial query.
source:Source.postprocess: when extracting @-mentions, defer to existing tag if it has the samedisplayNameand hasurl.
as1:get_ownerbug fix forpost,update,deleteactivities.activity_changed: add newinReplyTokwarg.is_public: add newunlistedkwarg.
as2:to_as1: bug fix, preserveobjectType: featuredfor banner/header images even whenmediaTypeis also set.is_public: add newunlistedkwarg.from_as1:- For
iconfield, prefer image types that are allowed by Mastodon. - Bug fix, handle
stop-followingwith stringobjectid.
- For
atom:- Add new
extract_entriesfunction. activity_to_atom: default actor/author name to username.atom_to_activities: support top-levelentryelement as well asfeed.atom_to_*:- add
object.author - default
objectTypetoarticle/noteandverbtopost - convert
link rel=self/alternatetourl - use
displayNamein objects instead oftitle - Interpret entry
linkwithoutrelas self link.
- add
- If
entry.authordoesn't have id or url, default them to feed author's.
- Add new
bluesky:- Implement
createandpreview. - Fully support both
recordandobjecttypes infrom_as1andto_as1. Useto_as1'stypekwarg andfrom_as1'sout_typekwarg to disambiguate. - Implement
Bluesky.post_id. - Add new
blob_to_urlfunction. - Delete
as1_to_profile, switchfrom_as1to return$type: app.bsky.actor.profile. - Convert HTML
summaryandcontentto plain text. - Implement
Bluesky.user_to_actor,Bluesky.get_actor. - Don't log in (fetch an access token) eagerly in the constructor; wait until the client makes a call.
- Prefer DID to handle in API calls that accept either.
at_uri_to_web_url: support lists.web_url_to_at_uri: convert profile URLs likehttps://bsky.app/profile/snarfed.orgto profile record URIs (at://snarfed.org/app.bsky.actor.profile/self) instead of repo URIs (at://snarfed.org).- Add
from_as1_to_strong_ref. - Allow
:s in record keys (atproto#2224). to_as1:- Convert blobs, both new and old style, to PDS
getBlobURLs. - Add new
urikwarg. - Translate
handletousername, add newrepo_handlekwarg. - Add support for
app.bsky.feed.repost,app.bsky.graph.defs#listView,app.bsky.feed.defs#blockedPost. - Add
actor/authorbased onrepo_did. - Improve
urlfield: include custom handles, only userepo_did/handleforapp.bsky.actor.profile. - Handle bad facet indices that point inside Unicode code points (example; discussion).
- Convert
!no-unauthenticatedlabel on profiles to AS1@unlistedaudience target (bridgy-fed#828).
- Convert blobs, both new and old style, to PDS
from_as1:- Add
out_typekwarg to specify desired output type, egapp.bsky.actor.profilevsapp.bsky.actor.defs#profileViewBasicvsapp.bsky.actor.defs#profileView. - Add
blobskwarg to provide blob objects to use for image URLs. - Add
clientkwarg to fetch and populate CIDs. - Handle mention tags pointing to bare DIDs.
- Use
parentasrootin replies. (Technically wrong in cases where the parent isn't the root, but we don't actually know the root. π€·) - Bug fix: handle bare string URLs in
imagefield. - Bug fix: handle tags without
urlfield. - Strip trailing slash from home page URLs in order to remove visible
/from rel-me verified links on Mastodon etc. - Convert
attributedToto singular if it has only one element. - If
nameisn't set, fall back topreferredUsernameor infer Webfinger handle fromidorurl. - Prioritize bsky.app profile URL before handle URL in
urlfield (bridgy#1640). - Convert
bsky.appinReplyToURLs toat://URIs. - Tighten up
datetimeconversion to match the ATProto recommended format.
- Add
- Implement
facebook:- Remove
Facebook.fql_stream_to_post. Facebook turned down FQL in 2016.
- Remove
github:- When converting data to AS1, use
displayNamein objects instead oftitle.
- When converting data to AS1, use
mastodon:get_activitiesbug fix: use query params for/api/v1/notificationsAPI call, not JSON body.- Convert HTTP 200 responses with
errorJSON field (eg from Sharkey) to 400/401 exceptions. - Prefer
media_attachments.remote_urlwhen available since it may be more long-lived thanurlfor remote statuses (bridgy#1675).
microformats2:object_to_jsonbug fix: handle singularinReplyTo.json_to_objectbug fix: handle list-valuedlocation.
nostr:get_*: return partial results when the websocket connection is closed prematurely.to_as1: handle invalid NIP05 values (eg{})
rss:to_activities:- Use
objectType: noteiftitleisn't set or is a prefix (possibly ellipsized) ofcontent/description. - Add support for images in
media:contenttags (#674).
- Use
Source:postprocess_activity/object: addmentionskwarg to convert @-mentions in HTML links tomentiontags.
Highlights: Nostr, Bluesky get_activities, lots of improvements in as2 and microformats2, and more!
REST API breaking changes:
Twitter is dead, at least in the REST API.
Non-breaking changes:
- Add new
nostrmodule! as1:- Add
get_owner,targets. - Add
accept,reject,stop-followingtoVERBS_WITH_OBJECTand removerepost, it's not an AS1 verb. - Handle
urlfield list values (even though it's invalid AS1).
- Add
as2:to_as1:- Improve
Videohandling: supportLinkobjects inurl, extract stream URLs and types from linktags. - Coerce non-float
latitudeandlongitudeto float, raiseValueErroron failure. - Put image attachments into
imageas well asattachments(bridgy-fed#429). - Handle Hubzilla's composite object attachment
values. - Bug fix for null
mediaTypeinattachmentandtags.
- Improve
- Add new
TYPES_WITH_OBJECTconstant. - Add new
get_urls,addressfunctions. - Improve
Content-Typecompatibility withapplication/ld+json; profile="https://www.w3.org/ns/activitystreams". - Bug fix for
Undoactivities with bare string idobjects. - Revise HTML in
PropertyValueattachments on actors to include full URL in anchro text to be compatible with Mastodon's profile link verification.
atom:activities_to_atometc:- Switch
contentfrom XHTML to HTML inside CDATA to support non-XHTML input content (bridgy-fed#624. - Bug fix, handle bare string URL
imagevalues. - Bug fix, emove incorrect
type="application/atom+xml"fromrel="self"linkinentry. - Render
objectType: commentattachments. - Remove invalid
<a>element for tags. - Bug fix: avoid encoded
<and>characters intitle(#629).
- Switch
- Bug fixes in
activity_to_atom/activities_to_atomfor dict-valuedurlfields. - Render images in article/note attachments.
- Render
objectType: serviceattachments, eg Bluesky custom feeds.
bluesky:- Implement
BlueskyAPI class, includingget_activities. - Drop bundled
app.bsky/com.atprotolexicons, use lexrpc's instead. - Convert reposts, quotes, inline links, attached links, and mentions, both directions. Includes Bluesky facet (rich text) support.
- Handle quote posts with attached images, both directions.
- Handle likes, both directions.
- Add new
web_url_to_at_urifunction. from_as1: handle link tags without start/end indices.to_as1:- Add new
typekwarg. - Generate staging.bsky.app profile and post URLs.
- Propagate profile
didinto actorid. - Add unimplemented stub for custom feeds, eg
app.bsky.feed.defs#generatorView.
- Add new
- Add
as1_to_profile. - Bug fix for converting follows, both directions:
subjectinapp.bsky.graph.followis followee, not follower. (That field is badly named!)
- Implement
jsonfeed:activities_to_jsonfeed:- Bug fix, handle bare string values for
imageandstream. - Bug fix: handle non-object
author.
- Bug fix, handle bare string values for
mastodon:status_to_object: add/fix alt text handling for images.
microformats2:json_to_html:- HTML-escape tag and quote attachment names. Fixes GHSA-4w4f-g49g-3f7j; thank you @janboddez!
json_to_object:- Improve handling of items with multiple types by using post type discovery more aggressively.
- Normalize ISO-8601 format of
publishedandupdatedtimestamps.
object_to_json:- Bug fix, handle bare string URL
imagevalues. - Normalize ISO-8601 format of
publishedandupdatedtimestamps. - Handle bare string ids for
repliesandshares(usually from AS2.)
- Bug fix, handle bare string URL
render_content:- Bug fix for bare string
authorandactorvalues.
- Bug fix for bare string
- Include
objectType: serviceattachments, eg Bluesky custom feeds, in JSON and HTML output.
rss:from_activities: handle bare string idauthor.
Breaking changes:
as2:- Interpret bare string
object,inReplyTo, etc values as ids, convert them to bare strings oridinstead ofurl.
- Interpret bare string
microformats2:- Convert simple string
in-reply-to,repost-of,like-ofetc values to AS1 bare strings orids instead ofurls.
- Convert simple string
Non-breaking changes:
- Add new
blueskymodule for Bluesky/AT Protocol! as1:- Add the
organizationobject type andACTOR_TYPESconstant (based on AS2). - Add new
get_ids,get_object, andget_objectsfunctions.
- Add the
activity_changed: ignoreinReplyTo.author(snarfed/bridgy#1338)as2:- Support converting between AS1
stop-followingand AS2UndoFollow. - Support AS2
AcceptandRejectfor follows as well as event RSVPs. - Add support for the
Question(ie poll),Organization, andDeleteobject types. - Convert
to/ccto/from AS1tofor public and unlisted. - Handle
type: Documentvideo attachments like Mastodon emits. from_as1: bug fix for image objects withurlandvaluefields (for alt text).from_as1: bug fix, handle bare string URLimagevalues.from_as1: converturls.displayNametoattachment.name(bridgy-fed#331).from_as1: preserveinReplyToobject values as objects, inline single-element lists down down to just single element.to_as1: useobjectType: featuredfor first image inimagefield.to_as1: populateactorintoobject.authorforUpdates as well asCreates.to_as1: convert Mastodon profile metadataPropertyValueattachments tourlcomposite objects withdisplayName.- Preserve
toandccvalues when converting both directions.
- Support converting between AS1
atom:- Bug fix for rendering image attachments without
imagefield to Atom. - Bug fix for
publishedandupdatedin entries with objects, eg likes, reposts, RSVPs, bookmarks. Thanks @gregorlove! (#480) - Bug fix for content
activity/ies_to_atomwhenobjectis present and empty. - Bug fix for objects with elements without
objectTypein thetofield.
- Bug fix for rendering image attachments without
flickr:get_activities: add support for thecountkwarg.
github:get_activities: add support for thecountkwarg.
jsonfeed:- Switch from
white-space: preCSS to converting newlines to<br>s because some feed readers follow it strictly and don't even line wrap (#456).
- Switch from
mastodon:- Add compatibility support for Truth Social.
- Handle truncated JSON API responses.
microformats2:json_to_object: drop backward compatibility support forlikeandrepostproperties. Background discussion.json_to_object: add newrel_urlskwarg to allow attachingdisplayNames tourlsbased on HTML text ortitleattribute (bridgy-fed#331).- Add new
json_to_activitiesfunction. hcard_to_html/maybe_linked_name: whennameis missing, use pretty URL as visible text.- Support the
h-cardorgproperty. json_to_object: handle compositersvpproperty value.json_to_object: bug fix whenfetch_mf2is True, handle when we run the authorship algorithm and fetch an author URL that has au-photowithalt.
rss:from_activities: fix item ordering to match input activities.
Breaking changes:
- Drop Python 3.6 support. Python 3.7 is now the minimum required version.
- Twitter, Instagram, Mastodon:
- Drop
get_activitiescachekwarg's support for App Engine memcache interface. It's now only used as a plaindict.get_activitieswill now make many small modifications, so if you pass an object that implements those as API calls, you'll probably want to batch those separately.
- Drop
- Twitter, Mastodon, Flickr, GitHub:
create/preview: support the AS1favoriteverb as well aslike. (bridgy#1345)
- Atom:
- Switch to converting AS1
id(instead ofurl) to Atomid.
- Switch to converting AS1
- Reddit:
- Implement
get_actor.
- Implement
- Mastodon:
create/preview: allow non-Mastodon replies, ie activities that includeinReplyToURLs even if none of them point to a toot. (bridgy#1321)- Raise
requests.HTTPErrorwithresponse.status_code502 instead ofJSONDecodeErroron non-JSON responses. This is synthetic, but more helpful for error handling.
- microformats2:
object_to_jsonand related functions: handle all escaped HTML entities, not just&<>.- Unify
microformats2.prefix_image_urlsandprefix_video_urlsinto a newas1.prefix_urlsfunction.
- RSS:
- Remove
itunes:category. It has to be one of Apple's explicit categories, which we aren't prepared to validate, so don't try.
- Remove
- ActivityStreams 2:
- Translate both
urlandurlsfrom AS1 into multi-valued AS2urlfield.
- Translate both
- Move a number of utility methods from the
Sourceclass to a newas1module:object_type,merge_by_id,is_public,add_rsvps_to_event,get_rsvps_from_event,activity_changed,append_in_reply_to,actor_name,original_post_discovery. as1.original_post_discovery: remove deprecatedcachekwarg.
Non-breaking changes:
- ActivityStreams 2:
- Fix spec compliance bug:
iconandimageare singly valued, not multiply valued. - Add new
is_publicmethod andPUBLIC_AUDIENCEconstant. - Prefer
"objectType": "featured"first in theimagefield when converting from AS1, last in theiconfield. This matches the ActivityPub (Mastodon) convention of usingiconfor profile pictures andimagefor header images. - Propagate
urlvalues into newPropertyValueattachments onPersonobjects; these end up in Mastodon's "profile metadata" link fields. to_as1: if an attachment'smediaTypeisimage/..., overrideobjectTypeand set it toimage.
- Fix spec compliance bug:
- Twitter
- Trim alt text in line between post preview and creation
- Correctly trim Twitter alt text
- Facebook
- Scraping: extract post id and owner id from
data-ftattribute and_ft_query param more often instead ofstory_fbid, which is now an opaque token that changes regularly. (facebook-atom#27)
- Scraping: extract post id and owner id from
- Instagram
- Add new
Instagram.scraped_json_to_activitiesmethod.
- Add new
- GitHub
createandpreview: convert profile URLs to @-mentions, eghttps://github.com/snarfedto@snarfed(bridgy#1090).get_activitieswithactivity_idnow supportsfetch_repliesandfetch_likes.
- Reddit
- Add
cachesupport toget_activities.
- Add
- REST API
- Add new
/scrapedendpoint that acceptsPOSTrequests with silo HTML as input. Currently only supports Instagram. Requiressite=instagram,output=...(any supported output format), and HTML as either raw request body or MIME multipart encoded file in theinputparameter.
- Add new
- microformats2
- Add new
extraandbody_classkwargs toactivities_to_html. - When converting
u-featuredimages to AS1, add new non-standard"objectType": "featured"field to distinguish them fromu-photo. - Convert
p-noteto AS1summary. - Bug fixes for converting
imageattachments tophoto.
- Add new
Source.original_post_discovery: add newmax_redirect_fetcheskeyword arg.
Breaking changes:
- Drop Python 3.5 support. Python 3.6 is now the minimum required version.
Non-breaking changes:
- RSS:
- Add support for RSS input via new
rss.to_activitiesfunction.
- Add support for RSS input via new
- Add new
include_shareskwarg toget_activities, implemented for Twitter and Mastodon. Defaults toTrue. IfFalse, shares (retweets in Twitter, boosts in Mastodon) will be discarded and not returned. Also add a correspondingsharesquery param to the REST API. - Instagram (scraping):
- Handle media items with no
userobject, add new fetch for comments. - Add
Instagram.merge_scraped_comments().
- Handle media items with no
- ActivityStreams 2:
- Handle error when
typeisn't a string.
- Handle error when
- Reddit:
- Implement
get_activities()to fetch posts by the current user or a user specified withuser_id.
- Implement
- Facebook scraping:
- Skip "Suggested for you" posts.
- Add
log_htmlkwarg toget_activities; defaults to False. - Miscellaneous bug fixes.
- JSONFeed:
- Handle malformed
items.authorelement.
- Handle malformed
Source.original_post_discovery: add newinclude_reserved_hostskwarg, defaults toTrue.- Instagram:
- Update scraping to handle new
feed_v2JSON format.
- Update scraping to handle new
- Facebook:
- Scraping: handle pictures, videos, link attachments, and text links in timeline/news feed posts.
- Mastodon:
- Bug fix for
get_activities()withfetch_mentions=True: handle notifications withstatus: null. Maybe happens when a status is deleted? create/preview_create: support bookmarks. (Nothing special happens with them; theircontentis posted as a normal toot.)
- Bug fix for
- microformats2:
- Stop rendering
image.displayNameas visible text in HTML, since it's already in the<img>'saltattribute. - Add
bookmark-ofsupport. - Add
prefix_image_urls()function. - Handle null
contentin AS1/2 objects. json_to_objectbug fix for compositebookmark-ofproperties.
- Stop rendering
- Twitter:
create/preview: support large videos via async upload. We now passmedia_category=tweet_videoto the chunked uploadINITstage, and then make blockingSTATUScalls until the video is finished processing. (bridgy#1043)create/preview: allow bookmarks. (bridgy#1045)create/preview: allow non-Twitter replies, ie activities that includeinReplyToURLs even if none of them point to a tweet. (bridgy#1063)get_activities: support list ids as well as slugs.- Bug fixes for removing t.co links to quoted tweets.
- Bug fix for multiple instances of the same link in tweet text.
get_activities(): raiseValueErroron invaliduser_id.
- REST API: ported web framework from webapp2 to Flask. No user-visible behavior change expected.
- Add Python 3.8 support, drop 3.3 and 3.4. Python 3.5 is now the minimum required version.
- Add Pixelfed! Heavily based on Mastodon.
- Standardize Instagram's and Facebook's scraping into new common
scraped_to_activities(),scraped_to_activity(), andmerge_scraped_reactions()methods. - Atom:
- Add the
summaryelement (#157).
- Add the
- REST API:
- Bug fix: URL-encode Unicode characters in
LinkHTTP headers (egrel=self,rel=header).
- Bug fix: URL-encode Unicode characters in
- Facebook:
- Scraping now uses mbasic.facebook.com instead of m.facebook.com.
- Flickr:
- Add support for adding tags to existing photos (bridgy#857).
get_comment(): skip fetching comments from API ifactivitykwarg is provided and contains the requested comment.
- GitHub:
- Handle HTTP 451 Unavailable for Legal Reasons responses (eg for DMCA takedowns) gracefully.
- Add create/preview support for reactions on pull review request comments (ie URLs with
#discussion_r...fragments).
- HTML/microformats2:
- Add
aria-hidden="true"to empty links (bridgy#947). - Bug fix: escape
&,<, and>characters in bare mf2contentproperties (aaronpk/XRay#102). json_to_object(): convertnicknametousername.
- Add
- JSON Feed:
- Gracefully handle when
content_htmlandcontent_textare incorrectly lists instead of strings.
- Gracefully handle when
- Instagram:
- Include threaded (ie nested) comments in scraping (bridgy#958).
- Mastodon:
- Bug fix for alt text with image attachments (bridgy#975).
- Omit empty
limitparam for compatibility with Pleroma (bridgy#977).
- Meetup:
create(): handle API errors and return the error message in theCreationResult(bridgy#921).
- Twitter:
- Bug fix: URL-encode list names in API calls.
- Bug fix: propagate alt text into AS1
photo.displayNameso that it gets all the way into microformats2 JSON and HTML (#183).
- Reddit:
- Implement
post_id(). - Cache user data fetched from the API for 5m to avoid repeating user profile API requests (bridgy#1021). when fetching multiple comments or posts from the same author
- Bug fix: use 'displayName' instead of 'name' in AS1 objects for submissions.
- Bug fix: use tag URIs for activity ids.
- Implement
- ActivityStreams 2:
to_as1(): forCreateactivities, include the activity actor's data in the object's author (snarfed/bridgy-fed#75).to_as1(): convertpreferredUsernametousername.from_as1(): convertusernametopreferredUsername.from_as1(): bug fix, makecontextkwarg actually work.
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
titleelement (#177). Background. - Always include author in items (#177).
- Bug fix: extract feed image from
hfeedcorrectly. - Bug fix: don't crash on
articleormentiontags in items with enclosures.
- Add
- Atom:
- Bug fix: extract feed image from
hfeedcorrectly.
- Bug fix: extract feed image from
- REST API:
- Add HTTP
HEADsupport. - 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
contextfields. html_to_activities(): limit toh-entry,h-event, andh-citeitems (#192).
- Don't crash on string
- The
cachekwarg 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
jsonmodule toujsonto speed up JSON parsing and encoding. - Add
durationandsizesupport 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 forsizeeverywhere. 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_userandxscookies 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 inCDATAsections. - 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
Mentiontags to AS1objectTypemention(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-categorymf2.
- 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_commentfield (#164). - Use cookie for all scraping HTTP requests, not just for likes.
- microformats2:
- Revise whitespace handling; use
white-space: preCSS in HTML output.
- Revise whitespace handling; use
- Facebook:
- Bug fix: don't interpret
photo.phpas username in post URLs.
- Bug fix: don't interpret
- Atom:
- Switch from
white-space: preCSS 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_createbug 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
Followsupport.
- 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_limitkwarg 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
tagsupport tocreate/preview_createto 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 returnValueErrorinstead ofAssertionErroron malformedactivity_idandcomment_idargs, 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
altattribute 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 thecategoryproperty, not full nested objects likeh-cards (#141).- Special case GitHub issues that are in-reply-to a repo or its
/issuesURL 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.jpgfiles 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_mf2kwarg tojson_to_object()for fetching additional pages over HTTP to determine authorship. - Generate explicit blank
p-namein HTML to prevent old flawed implied p-name handling (#131). - Fix
shareverb 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
updatedandpublished.
- 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-namelogic.)
- 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-audioclasses 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=alternatelink as well as actor/author URL (#151).
- Add
- JSON Feed:
- Fix bug that omitted title in some cases (#122).
- Add ActivityStreams 2.0! New
as2module 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
as2value forformatandinput. Revise existing ActivityStreams and microformats2 value names toas1,as1-xml, andmf2-json. Old valuesactivitystreams,json,json-mf2, andxmlare 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-featuredto ActivityStreamsimage. - Improve
h-eventsupport. - 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
readerquery 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-typeandactivity:verbelements when they have values. - Render AS image and mf2 u-photo if they're not already in content.
- Render
thr:in-reply-tofromobject.inReplyToas 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.publishactions. - 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-likeandu-repostproperties.
- 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.
