Lynn (of TITAA and general NLP wizardy fame) was gracious enough to lend me a Bluesky invite, so I could claim my handle on yet-another social media site. I’m still wary of it (as noted in one of this week’s Drops), but the AT protocol — whilst super (lacking a better word) “verbose” — is pretty usable, especially thanks to Ilya Siamionau’s atproto AT Protocol SDK for Python.
Longtime readers know I am most certainly not going to use Python directly, as such practice has been found to cause early onset dementia. But, that module is so well done that I’ll gladly use it from within R.
I whipped up a small R script CLI that will fetch my feed and display it via the terminal. While I also use the web app and the Raycast extension to read the feed, it’s a billion degrees outside, so used the need to stay indoors as an excuse to add this third way of checking what’s new.
Store your handle and app-password in BSKY_USER
and BSKY_KEY
, respectively, adjust the shebang accordingly, add execute permissions to the file and 💥, you can do the same.
#!/usr/local/bin/Rscript
suppressPackageStartupMessages({
library(reticulate, quietly = TRUE, warn.conflicts = FALSE)
library(lubridate, include.only = c("as.period", "interval"), quietly = TRUE, warn.conflicts = FALSE)
library(crayon, quietly = TRUE, warn.conflicts = FALSE)
})
# Get where {reticlulate} thinks your python is via py_config()$python
# then use the full path to
# /full/path/to/python3 -m pip install atproto
atproto <- import("atproto")
client <- atproto$Client()
profile <- client$login(Sys.getenv("BSKY_USER"), Sys.getenv("BSKY_KEY"))
res <- client$bsky$feed$get_timeline(list(algorithm = "reverse-chronological"))
for (item in rev(res$feed)) (
cat(
blue(item$post$author$displayName), " • ",
silver(gsub("\\.[[:digit:]]+", "", tolower(as.character(as.period(interval(item$post$record$createdAt, Sys.time()))))), "ago\n"),
italic(paste0(strwrap(item$post$record$text, 50), collapse="\n")), "\n",
ifelse(
hasName(item$post$record$embed, "images"),
sprintf(
green("[%s IMAGE%s]\n"),
length(item$post$record$embed$images),
ifelse(length(item$post$record$embed$images) > 1, "s", "")
),
""
),
ifelse(
hasName(item$post$record$embed, "external"),
yellow(sprintf(
"\n%s\n │\n%s\n\n",
bold(paste0(strwrap(item$post$embed$external$title, 47, prefix = " │"), collapse = "\n")),
italic(paste0(strwrap(item$post$embed$external$description, 47, prefix = " │"), collapse = "\n"))
)),
""
),
"\n",
sep = ""
)
)
This is a sample of the output, showing how it handles embeds and images:
Code is on GitLab.
FIN
There’s tons of room for improvement in this hastily-crafted bit of code, and I’ll get it up on GitLab once their servers come back to life.
If you want to experience Bluesky but have no account, the firehose — which Elon charges $40K/month for on the birdsite — is free and can be accessed sans authentication:
library(reticulate)
atproto <- import("atproto")
hose <- atproto$firehose$FirehoseSubscribeReposClient()
handler <- \(msg) {
res <- atproto$firehose$parse_subscribe_repos_message(msg)
print(res) # you need to do a bit more than this to get the actual commit type and contents
}
hose$start(handler)
You can find me over on bsky at @hrbrmstr.dev.
2 Trackbacks/Pingbacks
[…] Lynn (of TITAA and general NLP wizardy fame) was gracious enough to lend me a Bluesky invite, so I could claim my handle on yet-another social media site. I’m still wary of it (as noted in one of this week’s Drops), but the AT protocol — whilst super (lacking a better word) “verbose” — is pretty… Continue reading → […]
[…] wanted to see your favourite social media posts in your command line? No? Me neither, but at least hrbrmstr has a few months ago. Or to be honest, I don’t know which social media site he prefers, but […]