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.