hrbrmstr's Daily Drop

Share this post

Drop #305 (2023-07-28): Weekend Project Edition

dailyfinds.hrbrmstr.dev

Discover more from hrbrmstr's Daily Drop

A digest of all the interesting data, packages, blogs and papers covering lots of programming languages, CLI utilities, cybersecurity, data visualization, data science, web-scraping and more!
Continue reading
Sign in

Drop #305 (2023-07-28): Weekend Project Edition

Post It!

boB Rudis
Jul 28, 2023
Share this post

Drop #305 (2023-07-28): Weekend Project Edition

dailyfinds.hrbrmstr.dev
Share

Today, we build a small CLI utility to display Bluesky posts, given a Bluesky post URL.

BEFORE YOU CLICK AWAY (since not everyone has or wants access to Bluesky) 👉🏼 you do not need a Bluesky account to work on this week’s WPE. While said account is usually required to read Bluesky posts (unless you spy on the firehose), I’ve set up a REST API — that should work through and past the weekend — which handles one integral component: getting the JSON for the feed, given the correct parameters.

white clouds during daytime
Photo by Maddison McMurrin on Unsplash

Fundamentally, all that’s truly needed is curl, trurl (https://github.com/curl/trurl), and jq to complete this assignment, but you can use anything you like. Those three tools are available across all operating systems and drop in painlessly. So, we’ll look at what’s required to get everything we need to take in a Bluesky post/thread URL and retrieve the contents of it to do anything you like with it.

After you’re done, you’ll be able to view any post you have a URL (or just the right parameters) for.

Dipping Our Toes Into The Bluesky/AT Protocol Waters

persons feet on water
Photo by Carlos Felipe Vericat Sanz on Unsplash

To continue the metaphor used in the title: we’re most certainly not diving into the AT Protocol, today. Previous Drops have introduced it, and the focus on these Summer WPEs is to be able to build something pretty quickly.

Bluesky post URLs look like this:

https://bsky.app/profile/greynoise.bsky.social/post/3k3j5a5pl2x2r
  1. https://: This is the protocol used for secure communication over the internet.

  2. bsky.app: This is the domain name for the Bluesky app, which is a social networking application built on the AT Protocol.

  3. /profile: This part of the URL indicates that the following content is related to a user profile.

  4. greynoise.bsky.social: This is the unique identifier for the user’s profile on the Bluesky network. It consists of a username (greynoise) and the domain name (bsky.social) .

  5. /post: This part of the URL indicates that the following content is related to a specific post made by the user.

  6. 3k3j5a5pl2x2r: This is the unique identifier for the post within the user’s profile.

Getting the data we ultimately want is not as simple as just making a request to that URL and retrieving the contents. We’re going to need the user profile identifier (userId), post identifier (postId), from a post URL to be able to retrieve the post contents. From the example URL provided, those would be :

  • userId: greynoise.bsky.social

  • postId: 3k3j5a5pl2x2r

We can use the aforementioned trurl utility:

$ trurl --json "https://bsky.app/profile/greynoise.bsky.social/post/3k3j5a5pl2x2r" | jq -r '.[0].parts.path'
/profile/greynoise.bsky.social/post/3k3j5a5pl2x2r

and some regular expression capture groups to get those values:

postPath="$(trurl --json "https://bsky.app/profile/greynoise.bsky.social/post/3k3j5a5pl2x2r" | jq -r '.[0].parts.path')"

if [[ "${postPath}" =~ /profile/([^/]+)/post/(.+) ]]; then 
  userId="${BASH_REMATCH[1]}"
  postId="${BASH_REMATCH[2]}"
fi

NOTE: In zsh, BASH_REMATCH is just match.

Now we're nearly there!

Resolving Handles

brass door knob on brown wooden door
Photo by Super Snapper on Unsplash

The endpoint we would normally need to hit is app.bsky.feed.getPostThread. This is an authenticated endpoint, which is why I've set up a helper REST API for y'all. So, while you won't be hitting that URL directly, you will still need to provide the components for the uri that's listed in the parameters section of the getPostThread documentation.

This uri is is an AT URI. AT URIs are always a reference to individual records in a given repository. The AT URI for that the post we're using as an example is:

at://did:plc:3y5lhkm7gbrixowhpoum2zaq/app.bsky.feed.post/3k3j5a5pl2x2r

You may have noticed that the greynoise.bsky.social is missing! That's because we need to turn it into a decentralized identifier (DID). This is a persistent identifier that won't — or, at least, should not — change even if you handle changes. i.e., When I signed up for Bluesky, my original handle was hrbrmstr.bsky.social. I eventually set up my own handle — hrbrmstr.dev — and my DID remained the same.

Thankfully, Bluesky has an unauthenticated endpoint to turn registered handles into DIDs:

$ curl -s "https://bsky.social/xrpc/com.atproto.identity.resolveHandle?handle=${userId}" | jq -r '.did'
did:plc:3y5lhkm7gbrixowhpoum2zaq

That did is the last component you need to retrieve a Bluesky post from my REST API.

Post Retrieval

white and brown puppy petching wood
Photo by Rob Fuller on Unsplash

Armed with the did and the postId, you're ready to retrieve the contents of a Bluesky post.

The API server I have set up for this is at api.hrbrmstr.de (note the lack of a v!). The API path prefix is /bsky and it expects the did in the second path component, and the postId in the last. This is what the API call might look like (without using variables):

curl -s "https://api.hrbrmstr.de/bsky/did:plc:3y5lhkm7gbrixowhpoum2zaq/3k3j5a5pl2x2r"

Sample JSON output is in this snippet.

Putting It All Together

a person holding a drawing
Photo by Anthony Wade on Unsplash

This is a contiguous view of the bash code:

postURL="https://bsky.app/profile/greynoise.bsky.social/post/3k3j5a5pl2x2r"

postPath="$(trurl --json "${postURL}" | jq -r '.[0].parts.path')"

if [[ "${postPath}" =~ /profile/([^/]+)/post/(.+) ]]; then 

  userId="${BASH_REMATCH[1]}"
  postId="${BASH_REMATCH[2]}"

  did=$(curl -s "https://bsky.social/xrpc/com.atproto.identity.resolveHandle?handle=${userId}" | jq -r '.did')

  res=$(curl -s "https://api.hrbrmstr.de/bsky/${did}/${postId}")
  echo "${res}" | jq -r '.data.thread.post.record.text' | fold -w 60 -s

fi

You'll want to modify it to take a URL as a parameter and display more fields.

FIN

For those more JS-inclined, there's a Glitch project you can clone to hack and expand on (for free! And, with no need to install anything locally) as well.

I was going to stand up an OG tag/embed service based on this, but these folks already have, and did a bang-up job, so I moved my efforts to this WPE instead. ☮

Share this post

Drop #305 (2023-07-28): Weekend Project Edition

dailyfinds.hrbrmstr.dev
Share
Previous
Next
Comments
Top
New
Community

No posts

Ready for more?

© 2023 boB Rudis
Privacy ∙ Terms ∙ Collection notice
Start WritingGet the app
Substack is the home for great writing