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-called
lexicons, from which we
autogenerated R
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.
You can install the development version of atrrr
like so (install the
remotes
package first, with install.packages("remotes")
, if you
don’t have that yet):
# install.packages("remotes")
remotes::install_github("JBGruber/atrrr")
library(atrrr)
The first time you make a request, you will be prompted automatically to
enter your user handle and an app password to authenticate atrrr
to
communicate with BlueSky for you.
The page to generate app passwords is also automatically opened for you.
page to create new app passwordsHowever, 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.
To fetch all the skeets by a specific user, use the
get_skeets_authored_by
function. Note this also includes quote skeets
and reskeets. You can also opt not to parse the result by setting
parse = 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: 19
#> $ uri <chr> "at://did:plc:3ryku2cbgpazorytvt6lt273/app.bsky.feed.pos…
#> $ cid <chr> "bafyreiabtdobd6w4scrbpqkvpgvdwidmdo7gyj6vvvcayol2drjalw…
#> $ author_handle <chr> "beamagistro.bsky.social", "jbgruber.bsky.social", "jbgr…
#> $ author_name <chr> "Beatrice Magistro", "Johannes B. Gruber", "Johannes B. …
#> $ text <chr> "Excited to share my latest paper out at PSRM\n \"Attitu…
#> $ author_data <list> ["did:plc:3ryku2cbgpazorytvt6lt273", "beamagistro.bsky.…
#> $ post_data <list> ["app.bsky.feed.post", "2024-02-16T17:26:58.396Z", ["ap…
#> $ embed_data <list> ["app.bsky.embed.images#view", [["https://cdn.bsky.app/…
#> $ reply_count <int> 0, 1, 4, 0, 0, 4, 0, 1, 9, 0, 1, 0, 1, 0, 0, 0, 0, 1, 12…
#> $ repost_count <int> 6, 6, 8, 0, 0, 15, 2, 1, 440, 3, 1, 5, 28, 0, 0, 0, 0, 0…
#> $ like_count <int> 12, 20, 12, 0, 1, 43, 6, 1, 618, 7, 3, 10, 35, 0, 0, 1, …
#> $ indexed_at <dttm> 2024-02-16 17:26:58, 2024-01-10 15:23:18, 2024-01-04 16…
#> $ in_reply_to <chr> NA, NA, NA, NA, "at://did:plc:eotrvt2wp6mqooxjf3bzklwa/a…
#> $ in_reply_root <chr> NA, NA, NA, NA, "at://did:plc:eotrvt2wp6mqooxjf3bzklwa/a…
#> $ quotes <chr> NA, "at://did:plc:vgvueqvmbqgoyxtcdebqdcgb/app.bsky.feed…
#> $ tags <list> <NULL>, "rstats", "rstats", <NULL>, <NULL>, <NULL>, <NU…
#> $ mentions <list> <NULL>, <NULL>, <NULL>, <NULL>, <NULL>, <NULL>, <NULL>,…
#> $ links <list> <NULL>, <NULL>, <NULL>, <NULL>, <NULL>, <NULL>, <NULL>,…
#> $ is_reskeet <lgl> TRUE, TRUE, TRUE, FALSE, FALSE, TRUE, TRUE, FALSE, TRUE,…
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 posts
feeds <- 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 feed
rstat_posts <- get_feed(rstat_feed$uri, limit = 200) |>
dplyr::glimpse()
#> Rows: 200
#> Columns: 18
#> $ uri <chr> "at://did:plc:vgvueqvmbqgoyxtcdebqdcgb/app.bsky.feed.pos…
#> $ cid <chr> "bafyreifard344uolete7a4vhogz3ja67k6bxumijc6fsuocapqutgt…
#> $ author_handle <chr> "cranberriesfeed.bsky.social", "cranberriesfeed.bsky.soc…
#> $ author_name <chr> "CRAN Package Updates Bot", "CRAN Package Updates Bot", …
#> $ text <chr> "CRAN updates: minSNPs qris rgl SARP.compo #rstats\n", "…
#> $ author_data <list> ["did:plc:vgvueqvmbqgoyxtcdebqdcgb", "cranberriesfeed.b…
#> $ post_data <list> ["app.bsky.feed.post", "2024-03-05T09:02:16-06:00", [[[…
#> $ embed_data <list> <NULL>, <NULL>, ["app.bsky.embed.record#view", ["app.bs…
#> $ reply_count <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0,…
#> $ repost_count <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 2, 0, 0, 0, 0, 0, 0,…
#> $ like_count <int> 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 23, 3, 0, 0, 0, 0, 0, 0…
#> $ indexed_at <dttm> 2024-03-05 15:02:16, 2024-03-05 14:02:11, 2024-03-05 13…
#> $ in_reply_to <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
#> $ in_reply_root <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
#> $ quotes <chr> NA, NA, "at://did:plc:iqrcfhxwrm5tyf7vqhnfgv6j/app.bsky.…
#> $ tags <list> "rstats", "rstats", "rstats", "rstats", "rstats", "rsta…
#> $ mentions <list> <NULL>, <NULL>, <NULL>, <NULL>, <NULL>, <NULL>, <NULL>,…
#> $ links <list> <NULL>, <NULL>, <NULL>, <NULL>, <NULL>, <NULL>, <NULL>,…
Start with the Basic Usage vignette to learn more.
You can help by creating an issue 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 endpoint
app.bsky.actor.getProfiles
is accessible via atrrr:::app_bsky_actor_get_profiles()
. The function
get_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 at https://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 an issue first, so we don’t do duplicated work.
Footnotes
-
before 2024-01-04, this package was called
atr
. ↩