Tux the Penguin reading books

FOSS Academic

Sharing my book, one random bit at a time

No one will accuse me of being a Python expert, but I have increasingly been using it to take care of tasks. For example, if I build a website, I now often use Markdown or CSV files to store the data for the site, process it with Python, and then push it to whatever server its hosted on. This time, however, I made something that certainly was a challenge for me: a bot to post random bits of my new book to the fediverse.

Hard Stuff Made Easier By Others

I say this was challenging for me, and it was, but I think people with Python skills will see this as relatively easy.

I drew on the template provided by stfn on Codeberg. This approach is built for Mastodon and relies on the Mastodon.py project, which itself provides a wrapper around the Mastodon API.

The first step is initialization, where I get an access token from my Mastodon account and add that to a .env file. (This is a secret key, so if you do this, as well, don’t share your key!). I used my AoIR.social account because I am reasonably confident this bot would not run afoul of our Code of Conduct. In fact, I’m one of the admins there, so I am pretty sure we’re cool with this sort of thing – but of course, some fediverse instances don’t allow bots, so be sure to check on that.

As for the Python code itself, stfn’s approach breaks things down into to Python scripts: main.py and services.py. The main.py script is the one to run to actually post to the Mastodon account and really doesn’t need much modification.

The services.py account, by contrast, needs quite a bit. Here’s my version:

import os
import csv
import random
from mastodon import Mastodon


class ConfigService:
    """Service for validating environmental variables"""
    def validate_config(self) -> bool:
        return all(
            [
                os.getenv("CLIENT_ID"),
                os.getenv("CLIENT_SECRET"),
                os.getenv("ACCESS_TOKEN"),
                os.getenv("API_BASE_URL"),
            ]
        )


class BotService:
    """Service for generating the toots"""
    def prepare_toot_body(self):
        # opening the book CSV file and processing a quote
        with open('book.csv') as f:
          reader = csv.reader(f)
          bookquote = random.choice(list(reader))
          bookquote = str(bookquote)
          bookquote = bookquote.replace('\', \'', ',')
          bookquote = bookquote.replace('[\'', '')
          bookquote = bookquote.replace('\']', '')
          # trimming the quote back in characters to the nearest full stop
          bookquote = bookquote[:297]
          reversesearch = bookquote.rfind('. ')
          if(reversesearch != -1):
            bookquote = bookquote[:reversesearch+1]
          # writing out the post
          spoiler_text = "Daily random quote out of context from my academic book about Mastodon"
          body = '''The following is a random quote from my next book, Move Slowly and Build Bridges:

''' + bookquote + '''...

#MoveSlowlyAndBuildBridges #bot'''
          return body
          return spoiler_text


class MastodonService:
    """Service for handling comms with Mastodon"""
    def __init__(self):
        self.mastodon = Mastodon(
            client_id=os.getenv("CLIENT_ID"),
            client_secret=os.getenv("CLIENT_SECRET"),
            access_token=os.getenv("ACCESS_TOKEN"),
            api_base_url=os.getenv("API_BASE_URL"),
        )

    def post(self, body, **kwargs):
        self.mastodon.status_post(body, spoiler_text="Daily random quote out of context from my academic book about Mastodon", language="en", **kwargs)

As you can see, I used Python’s CSV and Random modules. I took the raw text of my book and converted it into a .CSV file, where each paragraph becomes a row. The Python reads the CSV file. Then I took the random module to select a random paragraph. After that, I trimmed the quote back to 297 characters to the nearest period. That way the “toot” would stay within the character limit.

I added a Content Warning because, although my instance might be OK with bots, I wanted to make sure it was crystal clear to other fediverse folks that these posts are automatically generated.

Finally, I copied the scripts over to a Raspberry Pi server I run and set it to post every day at 9.00 am EST.

Stuff I Wish I Did Differently

The bot is working well, but I wish I had done a few things differently.

First, I should have simply randomly sorted the CSV rows and then had the bot post them in that random order. That’s because the bot has already posted a couple passages twice.

Second, I should have double-checked the paragraphs. There are a few block quotes in the book – lengthy quotes, typically from interviewees. When those get posted, it appears I’m claiming their words as my own. When that happens, I use Mastodon’s edit feature to fix it, and I add a reply comment specifying where the quote comes from.

Finally, sometimes the bot doesn’t run, and I suspect it’s because the quote plus content warning and link are all over the character limit. I should have built in some logging to see when this happens and fix it.

However, over all this has been a fun project – a way to share bits of my book with the fediverse while also learning a bit more about Python and Mastodon bots.

Post Tags

Comments

For each of these posts, I will also post to Mastodon. If you have a fediverse account and reply to my Mastodon post, that shows up as a comment on this blog unless you change your privacy settings to followers-only or DM. Content warnings will work. You can delete your comment by deleting it through Mastodon.

Don't have a fediverse account and you want one? Ask me how! robertwgehl AT protonmail . com

Reply through Fediverse