Scroll to top

Dec 15, 2023

The home on the web of Colin Walker.
Find out more on /about, say /EHLO, or tune in to the daily RSS feed.

Dec 15, 2023#p2

Now that I've got everything working properly (and shouldn't be hitting any rate limits) I wanted to detail how the Bluesky integration is all set up.

As I mentioned before, I'm using Clark Rasmussen's simple BlueskyAPI library but have now forked it – more on that later.

Posting

When posting to the blog (except for certain conditions) I will send it over to Bluesky – I'll strip tags and decode any HTML entities then check the post length. For anything over 275 characters (Bluesky has a limit of 300) the post gets truncated at the nearest full-stop and a link card generated:

$args = [
  'collection' => 'app.bsky.feed.post',
  'repo' => $bluesky->getAccountDid(),
  'record' => [
    'text' => $htmlcontent,
    'createdAt' => date('c'),
    '$type' => 'app.bsky.feed.post',
    'embed' => [
      '$type' => 'app.bsky.embed.external',
      'external' => [
        'uri' => $postlink,
        'title' => $title,
        'description' => 'Read the full post on the blog...',
      ],
    ],
  ],
];

If I decide to have a featured image (add the 'feat' class to it) this gets uploaded via the uploadBlob endpoint:

$body = file_get_contents($imageUrl);
$response = $bluesky->request('POST', 'com.atproto.repo.uploadBlob', [], $body, 'image/'.$imgExt);
$image = $response->blob;

$embed = [
  'embed' => [
    '$type' => 'app.bsky.embed.images',
    'images' => [
      [
        'alt' => $imageAlt,
        'image' => $image,
      ],
    ],
  ],
];

I can then add it as the thumbnail for the link card:

$args['record']['embed']['external']['thumb'] = $image;

Or, for a short post, have it as a normal image:

$args['record'] = array_merge($args['record'], $embed);

The post is submitted to the com.atproto.repo.createRecord endpoint with the relevant body ($args), I get the at:// address of the item on Bluesky and attach it to the blog post in the database.

Replies

When viewing the blog, if a post has an at:// address attached this will get passed to the app.bsky.feed.getPostThread endpoint with the required thread depth (currently 2). If the returned data includes replies I pull out the array and reverse it (Bluesky has them newest first) then recursively get the author, avatar and content of each to be displayed under the post comments.

Limits

I was having a problem hitting the rate limits on the com.atproto.server.createSession endpoint so needed to be able to reuse sessions. This is where forking the library comes in.

When creating a session I now save the API refresh token (refreshJwt) to a PHP session variable. Instead of going straight to 'createSession' I check if the session variable exists and pass it to com.atproto.server.refreshSession to get a new apiKey. I've read that atprotocol refresh tokens last for 2 months so I could technically save them to a more permanent location (the database) but this is easiest for now.

If no refresh token exists (or one has expired – unlikely) then a new session is created as normal. Much better.