Syndicating to Bluesky

Last year I described how I syndicate my posts to different social networks.

Back then my approach to syndicating to Bluesky was to piggy-back off my micro.blog account (which is really just the RSS feed of my notes):

Micro.blog can also cross-post to other services. One of those services is Bluesky. I gave permission to micro.blog to syndicate to Bluesky so now my notes show up there too.

It worked well enough, but it wasn’t real-time and I didn’t have much control over the formatting. As Bluesky is having quite a moment right now, I decided to upgrade my syndication strategy and use the Bluesky API.

Here’s how it works…

First you need to generate an app password. You’ll need this so that you can generate a token. You need the token so you can generate …just kidding; the chain of generated gobbledegook stops there.

Here’s the PHP I’m using to generate a token. You’ll need your Bluesky handle and the app password you generated.

Now that I’ve got a token, I can send a post. Here’s the PHP I’m using.

There’s something extra code in there to spot URLs and turn them into links. Bluesky has a very weird way of doing this.

It didn’t take too long to get posting working. After some more tinkering I got images working too. Now I can post straight from my website to my Bluesky profile. The Bluesky API returns an ID for the post that I’ve created there so I can link to it from the canonical post here on my website.

I’ve updated my posting interface to add a toggle for Bluesky right alongside the toggle for Mastodon. There used to be a toggle for Twitter. That’s long gone.

Now when I post a note to my website, I can choose if I want to send a copy to Mastodon or Bluesky or both.

One day Bluesky will go away. It won’t matter much to me. My website will still be here.

Have you published a response to this? :

Responses

https://qubyte.codes/

Inspired by Jeremy’s post on how he syndicates to Bluesky, I thought I’d follow suit (many examples are useful when it comes to API integration work). A disclaimer though… I’m dubious of the long term prospects of Bluesky for reasons I won’t go into here. That being said, it’s currently a vibrant place, and syndicating from my site to other places keeps my content[1] in my hands.

My setup is a bit of a Rube Goldberg machine:

  • I publish notes (optionally with a photo) and bookmarks using Micropub endpoints.
  • The endpoints add the note or bookmark as a JF2 JSON file to the git repository of my personal site on GitHub.
  • A GitHub Actions workflow is triggered by the addition of a note or bookmark.
  • The workflow calls a Node.js script. It calls the script using my Bluesky handle, which is qubyte.codes for me, and an app password. Do not use your actual password! Bluesky makes it pretty easy to create an app password, and when you have one you can add it as a secret for Actions to use at the ./settings/secrets/actions path of your repo site. I’ve called my secret BLUESKYAPPPASSWORD.

The rest of this post is about the script itself. As a rule of thumb, I keep bits of shell glue in Actions workflows, and store the bulk of any logic in a discrete script. That makes it easy to call the script by hand for testing.

I publish notes (optionally with an image) and bookmarks using Micropub endpoints. The note or bookmark is added as a JF2 JSON file to the git repository of my personal site on GitHub. When such a file is created there, a GitHub Actions workflow is triggered, and this workflow in turn calls a Node.js script.

The script is a vanilla Node.js script. While Bluesky does provide API client libraries, I didn’t find it necessary to use one. I managed to write this without any third party libraries. API work is all achieved with plain old fetch.

There are a couple of local libraries which I’ve extracted into their own modules. The first is blueskyAuth, which looks like this:

const createSessionUrl = 'https://bsky.social/xrpc/com.atproto.server.createSession'; export default async function blueskyAuth(handle, appPassword) { const res = await fetch(createSessionUrl, { headers: { 'content-type': 'application/json' }, method: 'POST', body: JSON.stringify({ identifier: handle, password: appPassword }) }); if (!res.ok) { throw new Error( Bluesky responded with an unexpected status: ${res.status} ${await res.text()} ); } return res.json(); // { accessJwt, refreshJwt, did }
}

Bluesky mostly speaks JSON, which is convenient when working in Node.js. To create a session with Bluesky, you JSON encode an object containing your handle and an app password. If successful, the response contains three things of interest:

  • accessJwt: A short-lived token used for API requests.
  • refreshJwt: A token which can be exchanged for a new accessJwt and refreshJwt when the accessJwt expires.
  • did: An identifier. The initial “d”[2] stands for “distributed”, but I recently discovered that the DID plc method used by Bluesky is not actually distributed at all, thanks to Christine Lemmer-Webber’s excellent article on Bluesky and decentralization. I highly recommend reading it to dispel some myths around how decentralized Bluesky is, by design.

My script uses the accessJwt as a token and the did as an identifier in API requests. Since the session is only needed to post one document (and possibly an image), there’s no need to think about refreshing the token, so I don’t use the refreshJwt.

The rest of the script is about one or two API requests. When an image is to be uploaded, that happens first. There’s not much to say here beyond the size restrictions being quite low, and being constrained to JPEGs and PNGs. I don’t think they’re doing much processing of images, so I recommend removing metadata from image uploads. The response of the image upload contains a blob field, which is used as a reference to the image in the second API request in the form of an embedding.

The final request creates the “record” (the note or bookmark) on Bluesky. There’s a lot to unpack here. The creating a post is full of useful samples to help figure out the anatomy of a record creation body.

One fascinating aspect of Bluesky is how text is composed. Rather than some sort of markup language, it uses a more limited concept called rich text facets. Text is linear, and facets (for example, links) are attached to UTF-8 byte ranges of the text. This is awkward for many languages! For example, JavaScript uses UTF-16 to represent string internally (as was the trend in the mid-’90s), so the range of characters you get with naïve string work in JS will give you incorrect offsets. Thankfully Node.js has the venerable Buffer class, which can be used to represent strings as arrays of UTF-8 bytes. A Buffer.byteLength is all it takes to get the UTF-8 size of a string.

Anyway, I’ve put lots of annotations in the syndication script, so hopefully it serves as a useful example!

  1. I dislike the “content” term, but I can’t think of something better which encompasses writing and media.
  2. Unintentional reference to the racing manga.

4 Shares

# Shared by Brian David on Sunday, November 24th, 2024 at 4:00pm

# Shared by Jens Grochtdreis on Sunday, November 24th, 2024 at 4:00pm

# Shared by Gustomela 🪵🌰🐿️ on Sunday, November 24th, 2024 at 4:18pm

# Shared by Dawn Ahukanna on Sunday, November 24th, 2024 at 4:36pm

26 Likes

# Liked by cn 🦇 :marmite: 💉💉🚀 on Sunday, November 24th, 2024 at 3:28pm

# Liked by Dominik Schwind on Sunday, November 24th, 2024 at 3:51pm

# Liked by Rich Holman on Sunday, November 24th, 2024 at 3:51pm

# Liked by zeldman on Sunday, November 24th, 2024 at 3:51pm

# Liked by Pelle Wessman on Sunday, November 24th, 2024 at 3:51pm

# Liked by Brad Dielman on Sunday, November 24th, 2024 at 3:51pm

# Sunday, November 24th, 2024 at 3:51pm

# Liked by Richard Rutter on Sunday, November 24th, 2024 at 3:51pm

# Liked by Thomas Broyer on Sunday, November 24th, 2024 at 3:51pm

# Liked by Joe Casabona on Sunday, November 24th, 2024 at 3:51pm

# Liked by Phillip Lovelace on Sunday, November 24th, 2024 at 3:51pm

# Liked by Evil Jim O’Donnell on Sunday, November 24th, 2024 at 4:00pm

# Liked by depone on Sunday, November 24th, 2024 at 4:00pm

# Liked by Marya on Sunday, November 24th, 2024 at 4:18pm

# Liked by Dawn Ahukanna on Sunday, November 24th, 2024 at 4:36pm

# Liked by Craig Lee on Sunday, November 24th, 2024 at 4:36pm

# Liked by Łukasz Tyrala on Sunday, November 24th, 2024 at 5:52pm

# Liked by Jim Ray on Sunday, November 24th, 2024 at 6:27pm

# Liked by Ethan Marcotte on Sunday, November 24th, 2024 at 7:04pm

# Liked by Rob Lindsey on Sunday, November 24th, 2024 at 7:32pm

# Liked by Alex Blaine on Sunday, November 24th, 2024 at 9:01pm

# Sunday, November 24th, 2024 at 9:01pm

# Liked by Richard Rutter on Sunday, November 24th, 2024 at 10:31pm

# Liked by elusive t on Sunday, November 24th, 2024 at 11:02pm

# Liked by hansup on Monday, November 25th, 2024 at 7:21am

# Liked by Yann on Monday, November 25th, 2024 at 1:20pm

Related posts

Syndicating to Medium

POSSE: Publish (on your) Own Site, Syndicate Elsewhere.

Indy web

Maps—they don’t love you like I love you.

A little progress

Some code to show a progress bar for file uploads.

Notes from a small website

Posting to Twitter from adactio.com

Syndicating to Mastodon

Posting notes from my own website to my Mastodon account.

Related links

Pinboard is Eleven (Pinboard Blog)

I probably need to upgrade the Huffduffer server but Maciej nails why that’s an intimidating prospect:

Doing this on a live system is like performing kidney transplants on a playing mariachi band. The best case is that no one notices a change in the music; you chloroform the players one at a time and try to keep a steady hand while the band plays on. The worst case scenario is that the music stops and there is no way to unfix what you broke, just an angry mob. It is very scary.

Tagged with

New Facebook Platform Product Changes and Policy Updates - Facebook for Developers

Welp! As of today, none of my posts, links, or notes can be syndicated to Facebook:

The publish_actions permission will be deprecated. This permission granted apps access to publish posts to Facebook as the logged in user. Apps created from today onwards will not have access to this permission. Apps created before today that have been previously approved to request publish_actions can continue to do so until August 1, 2018.

If you’re reading this on Facebook: so long, it’s been good to know ya.

Tagged with

POSSE: a better way to post on social networks - The Verge

A good overview of syndicating from your own website to social network silos:

The platform era is ending. Rather than build new Twitters and Facebooks, we can create a stuff-posting system that works better for everybody.

References and contributors include Cory Doctorow, Manton Reece, Matt Mullenweg and, of course, Tantek.

Tagged with

Syndicating Posts from Your Personal Website to Twitter and Mastodon · Matthias Ott – User Experience Designer

A very timely post on using If This Then That to automatically post notes from your own site (via RSS) to Twitter and Mastodon.

I’ve set this up for my Mastodon profile.

Tagged with

Tagged with

Previously on this day

3 years ago I wrote Faulty logic

CSS logical properties here, they just aren’t evenly distributed yet.

6 years ago I wrote Conferencing

November was a busy month for events.

8 years ago I wrote Between the braces

The two faces of CSS.

17 years ago I wrote BarCamp begins

The geeks do their thing.

17 years ago I wrote Return to London town

BarCamp London 3: this time it’s personal.

23 years ago I wrote Mac Expo

This is my first "remote" posting.