Bluesky API Python experiments
Bluesky has been a big surprise recently.
I joined last year when it was fresh and new, and understandably it was quite quiet.
For a while I thought that Threads would beat it, as that platform had a couple of advantages, not least the influx of Instagram users.
But the winner, for me, and for now, has been Bluesky.
One of the lovely things about the Bluesky system is you can use your own domain as your handle. So instead of being in a rush to pick a handle before someone else grabs it, you can be more "you". Helps with imposters too! (You can follow me here)
Another nice thing is the feeds and lists - you are not forced to put up with whatever billionare owner or tech team wants to prioritize.
You can see what your friends are saying, what your friends are finding interesting, you can even make a custom feed based on whatever criteria you choose.
While there are third party tools to make that approachable for non-coders, the API is where the granular magic happens.
This lead me to look into what their API is like, and I started with the basics of automating posting.
Import the ATProto API
As usual, you can install with pip install atproto
and then we need the Client class, the utils, and the models.
My other imports here are sys
because we are using command line parameters, and PIL
because we want to post images of the correct aspect ratio as attachments.
import sys
from PIL import Image
from atproto import Client, client_utils, models
Authentication
Right now the API is using basic login authentication, which is easy for us, but has obvious security disadvantages.
def login(handle, password):
client = Client()
profile = client.login(handle, password)
print('Logged in as ', profile.display_name)
return client
Posting a Message
(I refuse to call Bluesky posts skeets)
def post(client, message, anchortext='', link=''):
if link !='':
text = client_utils.TextBuilder().text(message).link(anchortext, link)
else:
text = client_utils.TextBuilder().text(message)
post = client.send_post(text)
return post
Here you can see we optionally have a link attached, those will be formatted with blue anchor text rather than the text of the URL like monsters.
Posting Images
As mentioned above, we either need to crop our images to 1:1 ratio, or we need to provide the height and the width to prevent the image being distorted:
def get_size(imagefile):
# get image dimensions
img = Image.open(imagefile)
return img.width, img.height
def post_image(client, text, imagefile, alt_text=''):
# the path to our image file
with open(imagefile, 'rb') as f:
img_data = f.read()
# get width and height
width, height = get_size(imagefile)
# Add image aspect ratio to prevent default 1:1 aspect ratio
# Replace with your desired aspect ratio
aspect_ratio = models.AppBskyEmbedDefs.AspectRatio(height=height, width=width)
client.send_image(
text=text,
image=img_data,
image_alt=alt_text,
image_aspect_ratio=aspect_ratio,
)
With the image size retreived we simply need to supply the image data along with the descriptive alt-text of the image.
There's probably a way to use PIL to provide the image data too rather than load it twice but this was an experiment not production code!
Getting a user's feed
One of my use-cases is to syndicate my posts to other platforms where I spend less time. How do we get our most recent posts?
Turns out really easy to get the feed of any Bluesky handle:
def get_user_feed(client, handle):
print(f'\nProfile Posts of {handle}:\n\n')
# Get profile's posts. Use pagination (cursor + limit) to fetch all
profile_feed = client.get_author_feed(actor=handle)
return profile_feed.feed
Pulling it together
if __name__ == '__main__':
if len(sys.argv) < 3:
print("Please provide handle and password")
sys.exit(1)
handle = sys.argv[1]
password = sys.argv[2]
client = login(handle, password)
In my test I use the command line to get the handle and password, this avoids storing them in plain text on Github!
# New post with link
link = "https://github.com/omiq/bluesky"
result = post(client, "I think I will work on a #WordPress plugin that posts newly published articles (after a short delay). Will add to my repo here any experiments I make: ", "Github", link)
print(result)
# Post picture of kitteh
post_image(client, "Kara", "./kara.jpg")
# Get most recent post if it is not a reply and contains an image
feed = get_user_feed(client, "chrisg.com")
post = feed[0]
if(post.post.record.reply==None & post.post.embed != None):
print('\n\n', post.post.record.text, post.post.embed.images[0].fullsize, post.post.embed.images[0].alt)
If you found this interesting please follow me and say hi.
I'm resisting signing up at the moment, but if that becomes the default short post site then I may consider it. I could be tempted to do automatic posts about my solar panels as @edent does on Mastodon, but then I could do that on Hive too.
For now I have enough social media going on to occupy my time, but it's good to have options.
!BEER
View or trade
BEER
.Hey @makerhacks, here is a little bit of
BEER
from @steevc for you. Enjoy it!Did you know that <a href='https://dcity.io/cityyou can use BEER at dCity game to buy cards to rule the world.
Cool that you share. I actually prefer to use web3 instead of another web2 alternative :)
The niches I follow aren’t on these sites and up until recently I had to follow them on Twitter and facebook groups so I’m happy there’s some competition forming :)
Competition is always a good thing! :)
Thanks for your contribution to the STEMsocial community. Feel free to join us on discord to get to know the rest of us!
Please consider delegating to the @stemsocial account (85% of the curation rewards are returned).
You may also include @stemsocial as a beneficiary of the rewards of this post to get a stronger support.