Package: granary
granaryThe social web translator. Fetches and converts data between social networks, HTML and JSON withmicroformats2,ActivityStreams/ActivityPub,Atom,JSON Feed, and more.AboutUsingUsing the REST APIUsing the libraryTroubleshooting/FAQFuture workDevelopmentRelease instructionsRelated workChangelogAboutGranary 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 APIsInstagram and Facebook scraped HTMLActivityStreams1.0 and 2.0 JSON, includingActivityPubHTML and JSON withmicroformats2Atom,RSS 2.0,JSON FeedPlain XMLBluesky/AT ProtocolNostr, with manyNIPsFreeyourself from silo APIchaffand expose the sweet social data foodstuff inside in standard formats and protocols!Here's how to get started:Granary isavailable on PyPi.Install withpip install granary.Getting started docs.Reference docs.REST API and demo app atgranary.io.Source code on GitHub.License: This project is placed in the public domain.UsingThe library and REST API are both based on theOpenSocial Activity Streams service. Let's start with an example. This code using the library:fromgranaryimporttwitter...tw=twitter.Twitter(ACCESS_TOKEN_KEY,ACCESS_TOKEN_SECRET)tw.get_activities(group_id='@friends')is equivalent to thisHTTP GETrequest:https://granary.io/twitter/@me/@friends/@app/
  ?access_token_key=ACCESS_TOKEN_KEY&access_token_secret=ACCESS_TOKEN_SECRETThey 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_IDis a source-specific id or@mefor the authenticated user.GROUP_IDmay be@all,@friends(currently identical to@all),@self,@search, or@blocks;APP_IDis currently ignored; best practice is to use@appas a placeholder.Paging is supported via thestartIndexandcountparameters. They're self explanatory, and described in detail in theOpenSearch specandOpenSocial spec.When using theGROUP_ID@search(for platforms that support it — currently Twitter and Instagram), provide a search string via theqparameter. The API is loosely based on theOpenSearch spec, theOpenSocial Core Container spec, and theOpenSocial Core Gadget spec.Output data isJSON Activity Streams 1.0objects wrapped in theOpenSocial envelope, which puts the activities in the top-levelitemsfield as a list and adds theitemsPerPage,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-dropinsis 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 APIendpoints abovefor examples.Using the REST APITheendpoints aboveall serve theOpenSocial 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.FORMATmay beas1(the default),as2,atom,html,jsonfeed,mf2-json,rss, orxml(the default).atomsupports a booleanreaderquery parameter for toggling rendering appropriate to feed readers, e.g. location is rendered in content whenreader=true(the default). The rest of the path elements and query params aredescribed 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 thecache=falsequery parameter to your request.Include theshares=falsequery 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 correspondingendpoint above.Facebook and Instagram are disabled in the REST API entirely, sadly.Using the librarySee theexample abovefor a quick start guide.Clone or download this repo into a directory namedgranary. 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 arequestattribute for the current HTTP request.The useful methods areget_activities()andget_actor(), which returns the current authenticated user (if any). See thefull reference docsfor details. All return values are Python dicts of decoded ActivityStreams 1 JSON.Themicroformats2.*_to_html()functions are also useful for rendering ActivityStreams 1 objects as nicely formatted HTML.Troubleshooting/FAQCheck out theoauth-dropins Troubleshooting/FAQ section. It's pretty comprehensive and applies to this project too.Future workWe'd love to add more sites! Off the top of my head,YouTube,Tumblr,WordPress.com,Sina Weibo,Qzone, andRenRenwould 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.DevelopmentPull requests are welcome! Feel free toping me in #indieweb-devwith any questions.First, fork and clone this repo. Then, install theGoogle Cloud SDKand rungcloud components install beta cloud-datastore-emulatorto install thedatastore emulator. Once you have them, set up your environment by running these commands in the repo root directory:gcloudconfigsetprojectgranary-demo
python3-mvenvlocalsourcelocal/bin/activate
pipinstall-rrequirements.txt# needed to serve static files locallyln-slocal/lib/python3*/site-packages/oauth_dropins/staticoauth_dropins_staticNow, run the tests to check that everything is set up ok:gcloudbetaemulatorsdatastorestart--use-firestore-in-datastore-mode--no-store-on-disk--host-port=localhost:8089--quiet</dev/null>&/dev/null&python3-munittestdiscoverFinally, run the web app locally withflask run:GAE_ENV=localdevFLASK_ENV=developmentflaskrun-p8080Openlocalhost:8080and you should see the granary home page!If you want to work onoauth-dropinsat the same time, install it in editable mode withpip install -e <path to oauth-dropins repo>. You'll also need to update theoauth_dropins_staticsymlink, 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-qbetaappdeploy--no-cachegranary-demo*.yamlThe docs are built withSphinx, includingapidoc,autodoc, andnapoleon. Configuration is indocs/conf.pyTo build them, first install Sphinx withpip install sphinx. (You may want to do this outside your virtualenv; if so, you'll need to reconfigure it to see system packages withvirtualenv --system-site-packages local.) Then, rundocs/build.sh.Release instructionsHere's how to package, test, and ship a new release. (Note that this islargely duplicated in the oauth-dropins readme too.)Run the unit tests.sourcelocal/bin/activate.cshCLOUDSDK_CORE_PROJECT=granary-demogcloudbetaemulatorsdatastorestart--use-firestore-in-datastore-mode--no-store-on-disk--host-port=localhost:8089</dev/null>&/dev/null&sleep5python3-munittestdiscoverkill%1
deactivateBump the version number insetup.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 fromunreleasedto the current date.Bump theoauth-dropinsversion specifier insetup.pyto the most recent version.Build the docs. If you added any new modules, add them to the appropriate file(s) indocs/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 totest.pypi.orgfor testing.python3setup.pycleanbuildsdist
setenvverX.Ysourcelocal/bin/activate.csh
twineupload-rpypitestdist/granary-$ver.tar.gzInstall from test.pypi.org.cd/tmp
python3-mvenvlocalsourcelocal/bin/activate.csh
pip3uninstallgranary# make sure we force Pip to use the uploaded versionpip3install--upgradepip
pip3installmf2py==1.1.2
pip3install-ihttps://test.pypi.org/simple--extra-index-urlhttps://pypi.org/simplegranary==$verdeactivateSmoke test that the code trivially loads and runs.sourcelocal/bin/activate.csh
python3# run test code belowdeactivateTest code to paste into the interpreter:importjsonfromgranaryimportgithubgithub.__file__# check that it's in the virtualenvg=github.GitHub('XXX')# insert a GitHub personal OAuth access tokena=g.get_activities()print(json.dumps(a,indent=2))fromgranaryimportatomprint(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.gittag-av$ver--cleanup=verbatim
gitpush&&gitpush--tagsClick here to draft a new release on GitHub.EntervX.Yin theTag versionbox. LeaveRelease titleempty. Copy### Notable changesand the changelog contents into the description text box.Upload topypi.org!twineuploaddist/granary-$ver.tar.gzBuild the docs on Read the Docs: first chooselatestin the drop-down, then clickBuild Version.On theVersions page, check that the new version is active, If it's not, activate it in theActivate a Versionsection.Related workApache Streamsis 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 byPeople Pattern.Gnipsimilarlyconverts social network data to ActivityStreamsand supportsmany more source networks. Unfortunately, it's commercial, there's no free trial or self-serve signup, andplans start at $500.DataSiftlooks like broadly the same thing, except they offerself-serve, pay as you go billing, and they usetheir own proprietary output formatinstead of ActivityStreams. They're also aimed more at data mining as opposed to individual user access.Cliqset's FeedProxyused to do this kind of format translation, but unfortunately it and Cliqset died.Facebookused toofficiallysupportActivityStreams, 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.SocialSafeis one, although the SSL certificate is currently out of date.ThinkUpwas 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.Storytlris 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. theLifestream 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-shimaddsmicroformats2to Facebook and Twitter's raw HTML.sockethubis a similar "polyglot" approach, but more focused on writing than reading.Changelog6.1 - 2023-09-15Highlights: Nostr, Blueskyget_activities, lots of improvements inas2andmicroformats2, and more!REST API breaking changes:Twitter is dead, at least in the REST API.Non-breaking changes:Add newnostrmodule!as1:Addget_owner,targets.Addaccept,reject,stop-followingtoVERBS_WITH_OBJECTand removerepost,it's not an AS1 verb.Handleurlfield list values (even though it's invalid AS1).as2:to_as1:ImproveVideohandling: supportLinkobjects inurl, extract stream URLs and types from linktags.Coerce non-floatlatitudeandlongitudeto float, raiseValueErroron failure.Put image attachments intoimageas well asattachments(bridgy-fed#429).Handle Hubzilla's composite object attachmentvalues.Bug fix for nullmediaTypeinattachmentandtags.Add newTYPES_WITH_OBJECTconstant.Add newget_urls,addressfunctions.ImproveContent-Typecompatibility withapplication/ld+json; profile="https://www.w3.org/ns/activitystreams".Bug fix forUndoactivities with bare string idobjects.Revise HTML inPropertyValueattachments on actors to include full URL in anchro text to be compatible with Mastodon's profile link verification.atom:activities_to_atometc:Switchcontentfrom XHTML to HTML inside CDATA to support non-XHTML input content (bridgy-fed#624.Bug fix, handle bare string URLimagevalues.Bug fix, emove incorrecttype="application/atom+xml"fromrel="self"linkinentry.RenderobjectType: commentattachments.Remove invalid<a>element for tags.Bug fixes inactivity_to_atom/activities_to_atomfor dict-valuedurlfields.Render images in article/note attachments.RenderobjectType: serviceattachments, eg Bluesky custom feeds.bluesky:ImplementBlueskyAPI class, includingget_activities.Drop bundledapp.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 newweb_url_to_at_urifunction.from_as1: handle link tags without start/end indices.to_as1:Add newtypekwarg.Generate staging.bsky.app profile and post URLs.Propagate profiledidinto actorid.Add unimplemented stub for custom feeds, egapp.bsky.feed.defs#generatorView.Addas1_to_profile.Bug fix for converting follows, both directions:subjectinapp.bsky.graph.followis followee, not follower. (That field is badly named!)jsonfeed:activities_to_jsonfeed:Bug fix, handle bare string values forimageandstream.Bug fix: handle non-objectauthor.mastodon:status_to_object: add/fix alt text handling for images.microformats2:json_to_html:HTML-escape tag and quote attachment names. FixesGHSA-4w4f-g49g-3f7j; thank you@janboddez!json_to_object:Improve handling of items with multiple types by usingpost type discoverymore aggressively.Normalize ISO-8601 format ofpublishedandupdatedtimestamps.object_to_json:Bug fix, handle bare string URLimagevalues.Normalize ISO-8601 format ofpublishedandupdatedtimestamps.Handle bare string ids forrepliesandshares(usually from AS2.)IncludeobjectType: serviceattachments, eg Bluesky custom feeds, in JSON and HTML output.rss:from_activities: handle bare string idauthor.6.0 - 2023-03-22Breaking changes:as2:Interpret bare stringobject,inReplyTo, etc values as ids, convert them to bare strings oridinstead ofurl.microformats2:Convert simple stringin-reply-to,repost-of,like-ofetc values to AS1 bare strings orids instead ofurls.Non-breaking changes:Add newblueskymodule forBluesky/AT Protocol!as1:Add theorganizationobject type andACTOR_TYPESconstant (based on AS2).Add newget_ids,get_object, andget_objectsfunctions.activity_changed: ignoreinReplyTo.author(snarfed/bridgy#1338)as2:Support converting between AS1stop-followingand AS2UndoFollow.Support AS2AcceptandRejectfor follows as well as event RSVPs.Add support for theQuestion(ie poll),Organization, andDeleteobject types.Convertto/ccto/from AS1tofor public and unlisted.Handletype: 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.Preservetoandccvalues when converting both directions.atom:Bug fix for rendering image attachments withoutimagefield to Atom.Bug fix forpublishedandupdatedin entries with objects, eg likes, reposts, RSVPs, bookmarks. Thanks@gregorlove! (#480)Bug fix for contentactivity/ies_to_atomwhenobjectis present and empty.Bug fix for objects with elements withoutobjectTypein thetofield.flickr:get_activities: add support for thecountkwarg.github:get_activities: add support for thecountkwarg.jsonfeed:Switch fromwhite-space: preCSS to converting newlines to<br>s because some feed readers follow it strictly and don't even line wrap (#456).mastodon:Add compatibility support forTruth 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 newjson_to_activitiesfunction.hcard_to_html/maybe_linked_name: whennameis missing, use pretty URL as visible text.Support theh-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.5.0 - 2022-12-03Breaking changes:Drop Python 3.6 support. Python 3.7 is now the minimum required version.Twitter, Instagram, Mastodon:Dropget_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.Twitter, Mastodon, Flickr, GitHub:create/preview: support the AS1favoriteverb as well aslike. (bridgy#1345)Atom:Switch to converting AS1id(instead ofurl) to Atomid.Reddit:Implementget_actor.Mastodon:create/preview: allow non-Mastodon replies, ie activities that includeinReplyToURLs even if none of them point to a toot. (bridgy#1321)Raiserequests.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&amp;&lt;&gt;.Unifymicroformats2.prefix_image_urlsandprefix_video_urlsinto a newas1.prefix_urlsfunction.RSS:Removeitunes:category. It has to beone of Apple's explicit categories, which we aren't prepared to validate, so don't try.ActivityStreams 2:Translate bothurlandurlsfrom AS1 into multi-valued AS2urlfield.Move a number of utility methods from theSourceclass 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 newis_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.Propagateurlvalues into newPropertyValueattachments onPersonobjects; these end up in Mastodon's "profile metadata" link fields.to_as1: if an attachment'smediaTypeisimage/..., overrideobjectTypeand set it toimage.TwitterTrim alt text in line between post preview and creationCorrectly trim Twitter alt textFacebookScraping: extract post id and owner id fromdata-ftattribute and_ft_query param more often instead ofstory_fbid, which is now an opaque token that changes regularly. (facebook-atom#27)InstagramAdd newInstagram.scraped_json_to_activitiesmethod.GitHubcreateandpreview: convert profile URLs to @-mentions, eghttps://github.com/snarfedto@snarfed(bridgy#1090).get_activitieswithactivity_idnow supportsfetch_repliesandfetch_likes.RedditAddcachesupport toget_activities.REST APIAdd 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.microformats2Add newextraandbody_classkwargs toactivities_to_html.When convertingu-featuredimages to AS1, add new non-standard"objectType": "featured"field to distinguish them fromu-photo.Convertp-noteto AS1summary.Bug fixes for convertingimageattachments tophoto.Source.original_post_discovery: add newmax_redirect_fetcheskeyword arg.4.0 - 2022-03-23Breaking 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 newrss.to_activitiesfunction.Add newinclude_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 nouserobject, add new fetch for comments.AddInstagram.merge_scraped_comments().ActivityStreams 2:Handle error whentypeisn't a string.Reddit:Implementget_activities()to fetch posts by the current user or a user specified withuser_id.Facebook scraping:Skip "Suggested for you" posts.Addlog_htmlkwarg toget_activities; defaults to False.Miscellaneous bug fixes.JSONFeed:Handle malformeditems.authorelement.3.2 - 2021-09-15Source.original_post_discovery: add newinclude_reserved_hostskwarg, defaults toTrue.Instagram:Update scraping to handle newfeed_v2JSON format.Facebook:Scraping: handle pictures, videos, link attachments, and text links in timeline/news feed posts.Mastodon:Bug fix forget_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.)microformats2:Stop renderingimage.displayNameas visible text in HTML, since it's already in the<img>'saltattribute.Addbookmark-ofsupport.Addprefix_image_urls()function.Handle nullcontentin AS1/2 objects.json_to_objectbug fix for compositebookmark-ofproperties.Twitter:create/preview:support large videosvia 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.3.1 - 2021-04-03Add Python 3.8 support, drop 3.3 and 3.4. Python 3.5 is now the minimum required version.AddPixelfed! Heavily based on Mastodon.Standardize Instagram's and Facebook's scraping into new commonscraped_to_activities(),scraped_to_activity(), andmerge_scraped_reactions()methods.Atom:Add thesummaryelement (#157).REST API:Bug fix: URL-encode Unicode characters inLinkHTTP headers (egrel=self,rel=header).Facebook:Scraping now usesmbasic.facebook.cominstead ofm.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:HandleHTTP 451 Unavailable for Legal Reasonsresponses (eg for DMCA takedowns) gracefully.Add create/preview support for reactions on pull review request comments (ie URLs with#discussion_r...fragments).HTML/microformats2:Addaria-hidden="true"to empty links (bridgy#947).Bug fix: escape&,<, and>characters in bare mf2contentproperties (aaronpk/XRay#102).json_to_object(): convertnicknametousername.JSON Feed:Gracefully handle whencontent_htmlandcontent_textareincorrectlylists instead of strings.Instagram:Include threaded (ie nested) comments in scraping (bridgy#958).Mastodon:Bug fix for alt text with image attachments (bridgy#975).Omit emptylimitparamfor 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 AS1photo.displayNameso that it gets all the way into microformats2 JSON and HTML (#183).Reddit:Implementpost_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 authorBug fix: use 'displayName' instead of 'name' in AS1 objects for submissions.Bug fix: use tag URIs for activity ids.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.3.0 - 2020-04-08Breaking changes:Python 2 is no longer supported!Including theApp Engine Standard Python 2 runtime. On the plus side, thePython 3 runtimeis now supported! See thislist of differencesfor 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 forget_actor()withuser_id.Twitter:Addimage alt textsupport toget_activites()etc (#183).RSS:Additunes:image,itunes:author, anditunes:category.Strip HTML fromtitleelement (#177).Background.Always include author in items (#177).Bug fix: extract feed image fromhfeedcorrectly.Bug fix: don't crash onarticleormentiontags in items with enclosures.Atom:Bug fix: extract feed image fromhfeedcorrectly.REST API:Add HTTPHEADsupport.Add support for URL fragments withinput=html. If a fragment is provided, only that specific element is extracted and converted. (#185)GitHub:Publish: preserve<code>tags instead of converting them to `s so that GitHub renders HTML entities like&gt;inside them instead of leaving them escaped.Background.JSON Feed:Handle malformed attachments better.microformats2:Don't crash on stringcontextfields.html_to_activities(): limit toh-entry,h-event, andh-citeitems (#192).Thecachekwarg 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.2.2 - 2019-11-02Add Mastodon support!Add Python 3.7 support, and improve overall Python 3 compatibility.Update a number of dependencies.Switch from Python's built injsonmodule toujsonto speed up JSON parsing and encoding.Adddurationandsizesupport to ActivityStreams 1 and 2, RSS, and microformats2 HTML and JSON.microformats2 support is still emerging for both. Both integer seconds andISO 8601 string durationsare supported forduration. Integer bytes is used forsizeeverywhere. microformats2 HTML also includes human-readable strings, eg5.1 MB. (#169)Twitter:[preview]_create(): detect attempts to uploadimages over 5MBand return an error.Facebook:Addget_activities(scrape=True)for scraping HTML fromm.facebook.com. Requiresc_userandxscookies from a logged in session (snarfed/bridgy#886).Upgrade Graph API version from 2.10 to 4.0.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.)2.1 - 2019-09-04Convert AS2Mentiontags 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 fromperson-tagstomentions. Specifically, this means they'll no longer get rendered withu-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 newedge_media_to_parent_commentfield (#164).Use cookie for all scraping HTTP requests, not just for likes.microformats2:Revise whitespace handling; usewhite-space: preCSS in HTML output.Facebook:Bug fix: don't interpretphoto.phpas username in post URLs.Atom:Switch fromwhite-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.RSS:Default title to ellipsized content.2.0 - 2019-03-01Breaking change: drop Google+ sinceit shuts down in March. Notably, this removes thegoogleplusmodule.1.15 - 2019-02-28Add 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).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 sinceconsumer Google+ is shutting down entirely(more).Switch from deprecated global API endpoint to G+ endpoint. Background insnarfed/bridgy#846,Google blog postand docs.Instagram:Fix individual photo/video link urls for multi-photo/video posts.Handleuser-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.1.14 - 2018-11-12Adddelete(). 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:Adddelete()andpreview_delete()for deleting tweets.Flickr:Adddelete()andpreview_delete()for deleting photos.microformats2:Addfollow-ofsupport.Only use quotation-of property for quote tweets, not URLs. (#155)If a tag has startIndex/length, it gets linkified in the content, so don't also emit an mf2 child or HTML h-cite for it. (#155Atom:Encode&s in author URL and email address too. (Thankssebsued!)AS2:AddFollowsupport.1.13 - 2018-08-08Twitter:Support ISO 8601 formatted created_at timestamps, which thearchive 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 newignore_rate_limitkwarg toget_activities().GitHub:Addtagsupport 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.Atom:Shorten and ellipsize feed title when necessary (#144).microformats2:Upgrade mf2py to improve a few things likeimplied p-name detectionand whitespace handling (#142, fixes#145,snarfed/bridgy#756,snarfed/bridgy#828).Supportaltattribute in<img>tags (snarfed/bridgy#756).1.12 - 2018-03-24Add 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-linksallccTLDs.Background.GitHub:Bug fix forget_activities()with deleted issues and repos.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. Itshouldbe 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!1.11 - 2018-03-09Add GitHub!get_activities()supports issues and pull requests, including comments and reactions. It's currently based on notifications, so it's best effort, not comprehensive, and only includes recently active issues/PRs.create()andpreview_create()support issues, comments,stars, andreactions.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 newrecurring 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 whereget_activities(user_id=...)included the authenticated user's own recent photos, albums, and news publishes.Instagram:Extract more user (author) data from scraped profile pages.Fix home page feed scraping.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.Addfetch_mf2kwarg tojson_to_object()for fetching additional pages over HTTP to determine authorship.Generate explicit blankp-namein HTML to prevent old flawedimplied p-name handling(#131).Fixshareverb 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 inupdatedandpublished.JSON Feed:Omit title from items if it's the same as the content. (Often caused by microformats2's impliedp-namelogic.)1.10 - 2017-12-10Moved web site and REST API togranary.io!granary-demo.appspot.comnow 301 redirects.Twitter:Update the publish character limit to 280.Background.Fix abug in preview_create that auto-linked @-mentions inside URLs, e.g. Medium posts.Support videos and animated GIFs inget_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 microformats2u-photo,u-video, andu-audioclasses more often and consistently.Atom:Addatom_to_activities()for converting full feed documents.Add to REST API and web UI.Include source URL inrel=alternatelink as well as actor/author URL (#151).JSON Feed:Fix bug that omitted title in some cases (#122).1.9 - 2017-10-24AddActivityStreams 2.0! Newas2module includesto_as1()andfrom_as1()functions. Currently supported: articles, notes, replies, likes, reposts, events, RSVPs, tags, attachments.Atom:Add newatom_to_activity()function for converting Atom to AS1.Add email field to author, if provided.JSON Feed:Raise ValueError on bad (non-dict) input.REST API:Addas2value forformatandinput. Revise existing ActivityStreams and microformats2 value names toas1,as1-xml, andmf2-json. Old valuesactivitystreams,json,json-mf2, andxmlare still accepted, but deprecated.1.8 - 2017-08-29AddJSON Feedsupport to both library and REST API.Twitter:Addget_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.Facebook:Improve heuristic for determining privacy of wall posts from other users.Support GIFs in comments (attachment typesanimated_image_autoplayandanimated_image_share).Upgrade Graph API fromv2.6tov2.10.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:Addu-featuredto ActivityStreamsimage.Improveh-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.Google+:Update HTML scraping to handle changed serialized JSON data format.Atom:Add newactivity_to_atom()function that renders a single top-level<entry>instead of<feed>.Add newreaderquery 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 ASactivity:object-typeandactivity:verbelements when they have values.Render AS image and mf2 u-photo if they're not already in content.Renderthr:in-reply-tofromobject.inReplyToas well asactivity.context.inReplyTo.REST API:Fix bugs in html => json-mf2 and html => html conversions.Upgrade brevity to 0.2.14 for a couplebugfixes.1.7 - 2017-02-27microformats2:Interpret h-cite andu-quotation-of(experimental) as attachments, e.g. for quote tweets.Convertaudioandvideoproperties to AS attachments.Twitter:Linkify @-mentions and hashtags inpreview_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 whenget_activities()is passedgroup_id='@search'but notsearch_query.Instagram:Improve HTML scraping error handling.Supportmulti-photo/video posts.Facebook:Disable creating "interested" RSVPs, since Facebook's API doesn't allow it.Atom:Supportmedia enclosuresfor 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.1.6 - 2016-11-26Twitter: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-tweetsBug 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 articlewitha photo, but AS 'photo' typeisa 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.1.5 - 2016-08-25REST 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.1.4.1 - 2016-06-27Bump oauth-dropins requirement to 1.4.1.4.0 - 2016-06-27REST 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/changelogAdd reaction support.De-dupe event RSVPs by user.Twitter:Switch create() to use brevity for counting characters.https://github.com/kylewm/brevityFix 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.1.3.1 - 2016-04-07Updateoauth-dropinsdependency to >=1.3.1.3.0 - 2016-04-06Support posting videos! Currently in Facebook, Flickr, and Twitter.Instagram:Add support for scraping, since they'relocking down their API and requiring manual approval.Linkify @-mentions in photo captions.Facebook:FetchOpen Graph storiesakanews.publishactions.Many bug fixes for photo posts: better privacy detection, fix bug that attached comments to wrong posts.Twitter:Handle all photos/videos attached to a tweet, not just the first.Stop fetching replies to @-mentions.Atom:Render attachments.Addxml:base.microformats2:Load and convert h-card.Implement full post type discovery algorithm, using mf2util.https://indiewebcamp.com/post-type-discoveryDrop support for h-as-* classes, both incoming and outgoing. They're deprecated in favor of post type discovery.Drop old deprecatedu-likeandu-repostproperties.Misc bug fixes.Set up Coveralls.1.2.0 - 2016-01-11Improve 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.1.1.0 - 2015-09-06Add 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.1.0.1 - 2015-07-11Bug fix for atom template rendering.Facebook, Instagram: support access_token parameter.1.0 - 2015-07-10Initial PyPi release.