The goal of atrrr1 is to wrap the AT Protocol (Authenticated Transfer Protocol) behind Bluesky.And we have actually already fulfilled this goal!.
The entire protocol is open and documented in so-calledlexicons, from which we autogeneratedR functions.
These are not exported, however, since dealing with them is a bit advanced. Rather we have some nice human-generated functions with documentation and examples.
Installation
You can install atrrr from CRAN with:
install.packages("atrrr")You can install the development version ofatrrr like so (install theremotes package first, withinstall.packages("remotes"), if you don’t have that yet):
# install.packages("remotes")remotes::install_github("JBGruber/atrrr")Authentication
The first time you make a request, you will be prompted automatically to enter your user handle and an app password to authenticateatrrr to communicate with BlueSky for you.

The page to generate app passwords is also automatically opened for you.

However, you can also trigger this process manually:
auth("jbgruber.bsky.social")This can be useful if you want to replace an old token as it is permanently stored encrypted on disk.
Retrieve Skeets (get_skeets_authored_by)
To fetch all the skeets by a specific user, use theget_skeets_authored_by function.Note this also includes quote skeets and reskeets. You can also opt not to parse the result by settingparse = FALSE, however it is recommended to use the default parse option which results in a (more) tidy tibble.
get_skeets_authored_by(actor="benguinaudeau.bsky.social", parse=TRUE)|>dplyr::glimpse()#> Rows: 25#> Columns: 21#> $ uri <chr> "at://did:plc:ntd53albt5ffa4rgervvgibd/app.bsky.feed.pos…#> $ cid <chr> "bafyreiconry2rc74zkunyalbhwsxa347gxjnhb7uza7y4njnecu3ek…#> $ author_handle <chr> "jbgruber.bsky.social", "jacobmontgomery.bsky.social", "…#> $ author_name <chr> "Johannes B. Gruber", "Jacob Montgomery", "Beatrice Magi…#> $ text <chr> "I didn't even notice CRAN already approved it, but our …#> $ author_data <list> ["did:plc:ntd53albt5ffa4rgervvgibd", "jbgruber.bsky.soc…#> $ post_data <list> ["app.bsky.feed.post", "2024-10-04T12:43:04.752Z", ["ap…#> $ embed_data <list> ["app.bsky.embed.record#view", ["app.bsky.embed.record#…#> $ reply_count <int> 0, 0, 0, 1, 4, 0, 0, 4, 0, 1, 9, 0, 1, 0, 1, 0, 0, 0, 0,…#> $ repost_count <int> 5, 3, 6, 6, 8, 0, 0, 15, 2, 1, 432, 3, 1, 5, 28, 0, 0, 0…#> $ like_count <int> 11, 13, 13, 20, 13, 1, 1, 42, 6, 1, 606, 7, 3, 10, 35, 1…#> $ indexed_at <dttm> 2024-10-04 12:43:04, 2024-03-18 21:09:28, 2024-02-16 17…#> $ in_reply_to <chr> NA, NA, NA, NA, NA, NA, "at://did:plc:eotrvt2wp6mqooxjf3…#> $ in_reply_root <chr> NA, NA, NA, NA, NA, NA, "at://did:plc:eotrvt2wp6mqooxjf3…#> $ quotes <chr> "at://did:plc:vgvueqvmbqgoyxtcdebqdcgb/app.bsky.feed.pos…#> $ tags <list> "rstats", <NULL>, <NULL>, "rstats", "rstats", <NULL>, <…#> $ mentions <list> <NULL>, <NULL>, <NULL>, <NULL>, <NULL>, <NULL>, <NULL>,…#> $ links <list> <NULL>, <NULL>, <NULL>, <NULL>, <NULL>, <NULL>, <NULL>,…#> $ langs <list> ["en"], ["en"], ["it"], ["en"], ["en"], ["en"], ["en"],…#> $ labels <list> [], [], [], [], [], [], [], [], [], [], [], [], [], [],…#> $ is_reskeet <lgl> TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, TRUE, TRUE, …Analyzing Feeds on Blue Sky
On Blue Sky users have the ability to create custom feeds based on specific keywords. These feeds aggregate content, for instance, a user might curate a feed around the hashtag#rstats to gather all relevant content about. Let’s delve into the dynamics of such feeds.
Our starting point is to extract the posts from the#rstats feed created by “andrew.heiss.phd”.
# Fetching the feed postsfeeds<-get_feeds_created_by(actor="andrew.heiss.phd")# Filtering for a specific keyword, for example "#rstats"rstat_feed<-feeds|>filter(displayName=="#rstats")# Extracting posts from this curated feedrstat_posts<-get_feed(rstat_feed$uri, limit=200)|>dplyr::glimpse()#> Rows: 292#> Columns: 20#> $ uri <chr> "at://did:plc:7crs3axm67jhrc57gi7ojyjn/app.bsky.feed.pos…#> $ cid <chr> "bafyreicru2iryln3mqm6skdhym4illw2kn6z7qqig34trfvsp72zyi…#> $ author_handle <chr> "timbulwidodostp.bsky.social", "bigbookofr.bsky.social",…#> $ author_name <chr> "Timbul Widodo, S.TP", "Big Book of R", "Craig Hamilton"…#> $ text <chr> "Sequential analysis poisson binomial data g estimation …#> $ author_data <list> ["did:plc:7crs3axm67jhrc57gi7ojyjn", "timbulwidodostp.b…#> $ post_data <list> ["app.bsky.feed.post", "2024-11-16T13:08:30.723Z", [[[[…#> $ embed_data <list> <NULL>, <NULL>, <NULL>, ["app.bsky.embed.record#view", …#> $ reply_count <int> 0, 0, 0, 0, 1, 2, 0, 0, 1, 2, 1, 0, 0, 3, 1, 0, 0, 0, 0,…#> $ repost_count <int> 0, 0, 0, 3, 0, 19, 0, 1, 0, 2, 1, 4, 0, 11, 0, 0, 1, 0, …#> $ like_count <int> 0, 0, 0, 3, 1, 37, 0, 3, 1, 4, 1, 14, 1, 21, 1, 1, 4, 5,…#> $ indexed_at <dttm> 2024-11-16 13:08:30, 2024-11-16 12:31:26, 2024-11-16 12…#> $ in_reply_to <chr> NA, NA, "at://did:plc:ntd53albt5ffa4rgervvgibd/app.bsky.…#> $ in_reply_root <chr> NA, NA, "at://did:plc:ntd53albt5ffa4rgervvgibd/app.bsky.…#> $ quotes <chr> NA, NA, NA, "at://did:plc:ntd53albt5ffa4rgervvgibd/app.b…#> $ tags <list> <"RStats", "rstats", "rsoftware", "rstatistics">, <NULL…#> $ mentions <list> <NULL>, <NULL>, <NULL>, <NULL>, <NULL>, <NULL>, <NULL>,…#> $ links <list> <NULL>, <NULL>, <NULL>, <NULL>, <NULL>, <NULL>, <NULL>,…#> $ langs <list> ["en"], <NULL>, ["en"], ["en"], ["en"], ["en"], <NULL>,…#> $ labels <list> [], [], [], [], [], [], [], [], [], [], [], [], [], [],…Learn More?
Start with theBasic Usage vignette to learn more.
Want to help?
You can help by creating anissue requesting new features or reporting bugs.
If you are a developer, we are happy to accept pull requests. It should be fairly straightforward, as all endpoints are already covered by automatically generated function. For example, the endpointapp.bsky.actor.getProfiles is accessible viaatrrr:::app_bsky_actor_get_profiles(). The functionget_user_info() is just a thin wrapper around that and calls an optional parsing function:
get_user_info<-function(actor,parse =TRUE,.token =NULL) {# we need to use do.call so objects are passed to the right environment res<-do.call(what = app_bsky_actor_get_profiles,args =list( actor,.token = .token,# tokens are handled automatically under the hood.return ="json" ))|> purrr::pluck("profiles")if (parse) { res<-parse_actors(res) }return(res)}If you find an endpoint athttps://docs.bsky.app/docs/category/http-reference that interests you, you can write a similar wrapper and contribute it to the package (or build something new on top of it). But please open anissue first, so we don’t do duplicated work.
