Posting to Bluesky via the API from PHP – Part Six – Handling Handles and Oversized Images

I wrote a series of posts towards the end of the year that took you step-by-step through using the Bluesky API to post to the social network. Having used the code Patrick Delahanty got in touch to say that he had found a couple of issues. Firstly, handling user handles and secondly dealing with large images. Let’s take a look at both of those.

Handling User Handles

As with every social network, Bluesky allows you to select (or assigns for you) a unique user handle. As we saw in the last post I have changed mine to be my domain name so my Bluesky handle is @spokenlikeageek.com. You can include handles in your posts and what is supposed to happen is that is hyperlinked to the profile page but my code had not taken account of that.

To do this is a two-stage process. The first stage is, like with ordinary links, to parse the text looking for and marking any handles.

    function mark_mentions($connection, $text) {

        $spans = [];
        $mention_regex = '/[$|\W](@([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)/u';
        $text_bytes = mb_convert_encoding($text, 'UTF-8', 'ISO-8859-1');
        preg_match_all($mention_regex, $text_bytes, $matches, PREG_OFFSET_CAPTURE);

        $mentionsData = array();
        
        foreach ($matches[1] as $match) {
            $did = get_did_from_handle($connection, substr($match[0], 1));
            if (!empty($did->did)){
                $mentionsData[] = [
                    "start" => $match[1],
                    "end" => $match[1] + strlen($match[0]),
                    "did" => $did->did,
                ];    
            }
        }
        return $mentionsData;
    }

The observant amongst you will have spotted the get_did_from_handle function in the code above and that is the next stage. This is required because Bluesky wants you to do all the heavy lifting for it so not only do you need to work out the start and end positions of the handle you also need to provide the handle’s DID (Decentralized Identifier) too.

This is relatively straightforward to do as the Bluesky API provides a method that you can pass a handle and get in return the required DID. This works as follows:

    function get_did_from_handle($connection, $handle){

        $args = [
            'handle' => $handle,
        ];
    
        // send to bluesky to get the did for the given handle
        return  $connection->request('GET', 'com.atproto.identity.resolveHandle', $args);  

    }

Once again I feel duty-bound to point out that there is minimal error handling in this code and that, in this case, should no handle be returned you’ll get an empty mention array.

Handling Large Images

For reasons known only to Bluesky the maximum size of an image you can upload is 1 MB which, quite frankly, these days seems pretty small. However, comply we must.

When looking for solutions to this I did consider rejecting any image that was bigger than this limit but that would be pretty restrictive so instead I took a different approach. In fact, I took the same approach that Bluesky itself uses in its apps – repeatedly downsample the image until it is smaller than 1 MB.

I have added the following to the existing upload_media_to_bluesky function. This loops nine times each time reducing the file quality by 10%. In the first loop, the image is reduced to 90% of its original quality, and then the size of the new file is checked to see if it is under 1 MB. If it is then the loop is exited otherwise we go around again and this time the quality is reduced to 80% and so on.

        // does the file size need reducing?
        if ($size > maxUploadSize){
            $newImage = imagecreatefromstring($body);
            // downsample the image until it is less than maxImageSize (if possible!)
            for ($i = 9; $i >= 1; $i--) {

                imagejpeg($newImage, $fileUploadDir.'/'.$basename,$i * 10);
                $size = strlen(file_get_contents($fileUploadDir.'/'.$basename));

                if ($size < maxUploadSize) {
                    break;
                }else{
                    unlink($fileUploadDir.'/'.$basename);
                }

            }

            $body = file_get_contents($fileUploadDir.'/'.$basename);
            unlink($fileUploadDir.'/'.$basename);
        }

There are a few things to keep in mind about the above:

  1. there is a new constant, maxUploadSize, which is set to the current 1 MB limit. If Bluesky increases this at any point you can just change it here
  2. there is a new parameter to upload_media_to_bluesky called $fileUploadDir which, by default, is set to /tmp. You can set this to anything you like but make sure that you give your web server the appropriate permissions to write here
  3. the function uses imagejpeg which requires GD installed so make sure that you meet these requirements otherwise, it will fail.

You can download the updated code on my Github page and if you spot any other improvements I can make let me know.

Leave a Reply

Your email address will not be published. Required fields are marked *