Blep!

Creating a Mastodon bot in Python

Bleps always cheer me up, so I decided to create a Mastodon bot that posted one blep a day. (A blep is a picture of an animal with a tiny bit of its tongue sticking out, and Mastodon is a decentralized alternative to Twitter.)

Creating a Mastodon bot in Python was surprisingly easy, thanks to the Mastodon.py library. I’ve documented all the steps here, along with links to tutorials for each library or builtin feature that I used.

Have you created any Mastodon or Twitter bots? Let me know in the comments!

(Featured image: “blep” on dictionary.com)

I started by following Terence Eden’s guide (https://shkspr.mobi/blog/2018/08/easy-guide-to-building-mastodon-bots/) to create a basic script that would post one picture to Mastodon.

from mastodon import Mastodon
 
# base directory
basedir="/home/nroshak/blepbot/"
 
#   Set up Mastodon
mastodon = Mastodon(
    access_token = 'token.secret',
    api_base_url = 'https://botsin.space/'
)
# Post a blep
media = mastodon.media_post(basedir + "blep.jpg", description="Blep!")
mastodon.status_post("What a great blep!", media_ids=media)

Voilà! Running this python script makes a post to Mastodon. Notice that I’ve got the base directory explicitly named in the script. Eventually, cron is going to run this script with a very minimal set of environment variables, so if I forget to tell it where all the files are, it will error out.

Next, I wanted to add logging. There’s a good guide to the logging module on Real Python. To use the module, add “import logging” to the top of the script.

loglevel=logging.DEBUG
logging.basicConfig(level=loglevel,filename=basedir+"blepbot.log", filemode="w", format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%d-%b-%y %H:%M:%S')
logging.info('Running blepbot')

Now I needed to make the bot post a random image from a set of images, instead of always the same one! To get some test images, I gathered a bunch of blep pictures in a directory. I pulled down the first sixty results for “blep” from Google (image scraping Google search results is a whole other rabbit hole which I won’t go down here, since these aren’t the images I’ll be using for the actual bot) and stuck them in a directory called img/ under my basedir. To list the images and pick a random one, I’ll use os.listdir to list all the files in the img/ directory, and random.choice to pick one:

random_file=random.choice(os.listdir(basedir+"img"))

So I’ll need to also import random and os. The whole basic script is now:

from mastodon import Mastodon
import os, random,logging
 
#base directory
basedir="/home/nroshak/blepbot/"
#   Set up Mastodon
mastodon = Mastodon(
    access_token = basedir+'token.secret',
    api_base_url = 'https://botsin.space/'
)
loglevel=logging.DEBUG
logging.basicConfig(level=loglevel,filename=basedir+"blepbot.log", filemode="a", format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%d-%b-%y %H:%M:%S')
logging.info('Running blepbot')
random_file=random.choice(os.listdir(basedir+"img"))
img_file=basedir+"img/"+random_file
logging.debug('chose file '+img_file)
media = mastodon.media_post(img_file, description="Blep!")
mastodon.status_post("Daily blep", media_ids=media)

This actually works great, and I could have stopped here and had a working bot. But there’s a problem: I don’t have attribution for any of these images. I just pulled them down from Google without checking their licenses, or verifying that the sites that posted them actually had the right to post them, etc.

I didn’t feel great about that. For one thing, this set of images could include someone’s dead pet; blepbot’s purpose is to bring a little joy to people’s timelines, and seeing your dead pet show up uncredited on your timeline wouldn’t exactly be a joyful moment.

Plus, I wanted blepbot to be able to post my friends’ pets’ bleps and credit them for those pictures. So I needed a way to attach credit to each image. I decided to create custom post text for each image, which lets me include appropriate tags like “#CatsOfMastodon” and the blepping animal’s name, as well as credit.

Since I was going to be hand-populating the post text, I knew I’d be dealing with a pretty small set of images: probably a couple of hundred, enough to toot one a day for a year without repeats. Rather than setting up a database for such a small number of files, I decided to go with a quick and dirty route: next to the img/ directory, I placed a txt/ directory to hold one file of post text for each image file. The post text for each image is “image name”.txt. For example, “img/crazy.jpg” has accompanying text “txt/crazy.jpg.txt” . I got rid of all the scraped test images in img/, then hand-populated the img/ and txt/ directories with 50+ hand-selected bleps and accompanying post texts.

To get the post text, all I have to do is build the text file name from the image file name, open it, and read its contents into a variable, which I’ve very creatively called post_text. (GeeksForGeeks has a tutorial on reading from a file in Python here) Then I can make the post using post_text instead of “Daily blep” .

post_textfile=basedir+"txt/"+random_file+".txt"
media = mastodon.media_post(img_file, description="Blep!")
logging.debug ('found post text file '+post_textfile)
file1=open(post_textfile,"r")
post_text=file1.read()
file1.close()
mastodon.status_post(post_text,media_ids=media)

However, I’m going to mess up at some point and mis-name one of these text files, so I’d better check to make sure the file actually exists! If not, I’ll fall back to “Daily blep” for the post text and log an error:

if os.path.isfile(post_textfile):
    logging.debug ('found post text file '+post_textfile)
    file1=open(post_textfile,"r")
    post_text=file1.read()
    file1.close()
    mastodon.status_post(post_text,media_ids=media)
else:
    logging.error ('**** post text file '+post_textfile+' not found!')
    mastodon.status_post("Daily blep", media_ids=media)

And that’s it! The bot is done. All that’s left is to create a script to set up the correct Python environment and run it. I used virtualenv to set up my environment (there’s a very-easy-to-follow tutorial on python-guide: https://docs.python-guide.org/dev/virtualenvs/#lower-level-virtualenv), so setting up the environment in a script is very simple:

#!/bin/bash
 
source $HOME/blepbot/venv/bin/activate
python3 $HOME/blepbot/blepbot.py

I called this script “run_bot.bash”. I’m in a Linux environment, so all I had to do to get it to run daily was to add it to my user crontab:

$ crontab -l
SHELL=/usr/bin/bash
# run blepbot every day at 1:10 pm
10 13 * * * $HOME/blepbot/run_bot.bash >> $HOME/blepbot/out.txt 2>&1

Blepbot is now live and blepping on Mastodon at https://botsin.space/@blepbot . Follow Blepbot for your daily blep.

Possible Improvements:

  • Right now the alt text for each image is “Blep!” This isn’t a great description, and it’s a shame to lose the very descriptive alts from the images that Mastodon users have given me permission to share with Blepbot. It wouldn’t be hard to add an “alt/” directory for alt texts, and default to “Blep!” when no alt text is found.
  • Error handling – I did add error handling to do a simple pause-and-retry if the post failed, but Mastodon.py has a bunch of error classes I haven’t really gotten into.
  • I also added a “dev/prod” toggle, to let me try new features by making private test posts.
  • Mastodon has some fun day-specific tags for animal pics, like #TonguesOutTuesday, #WhiskersWednesday, etc., which I could append to the post text on the appropriate days.

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.