Autotooting with Mastodon

Earlier this week I followed the cool kids to Mastodon and started an account, which means I have to start playing around with the API. In particular, I wanted to have a script that would toot1 a link to a recently published blog post. Here are the steps I followed to write one.

First, I created an app from the command line using cURL:

curl -X POST -d "client_name=leantusk&redirect_uris=urn:ietf:wg:oauth:2.0:oob&scopes=write read&website=http://leancrew.com" https://mastodon.cloud/api/v1/apps

Let’s break this down. To register my Mastodon app, I post the necessary data to the apps method of the API hosted on mastodon.cloud, which is the instance (i.e., server) my account is at. That explains the URL at the end of the line and the -X POST switch.

The string argument of the -d switch is the data that the apps method needs. The individual pieces of data are (and have to be) separated by ampersands in the curl invocation, but they’re easier to read and understand if we split them into separate lines:

client_name=leantusk
redirect_uris=urn:ietf:wg:oauth:2.0:oob
scopes=write read
website=http://leancrew.com

After running this command, I got a JSON reply that looked like this:

{"id":"207492",
"name":"leantusk",
"website":"http://leancrew.com",
"redirect_uri":"urn:ietf:wg:oauth:2.0:oob",
"client_id":"alongstringofcharacters",
"client_secret":"anotherlongstring"}

What we’ll need for the next step are the client_id and client_secret strings, which look like ASCII hex dumps to me. They’re both 64 characters long, which presumably translates to 32 bytes.

The second step of registration is to get an authorization token, which will be included in every request made by the script to the API. The registration and authorization of an app is broken into two steps because in the usual case the app maker and the user are two different people.

I got my token through another invocation of cURL:

curl -X POST -d "client_id= alongstringofcharacters&client_secret= anotherlongstring&scope=write read&grant_type=password&username=myusername&password=mypassword" https://mastodon.cloud/oauth/token

Here, the information is posted to oauth/token at my instance, and the data can be broken down into these parts:

client_id= alongstringofcharacters
client_secret= anotherlongstring
scope=write read
grant_type=password
username=myusername
password=mypassword

The output of this command looked like

{"access_token":"another32bytehexdump",
"token_type":"Bearer",
"scope":"write read",
"created_at":1234567890}

where the key piece of information is the access_token. This will be included in the header to every request my script makes to the API.

Here’s the script, which I call tootpost:

python:
 1:  #!/usr/bin/env python
 2:  
 3:  import requests
 4:  from os import environ
 5:  
 6:  # Mastodon information
 7:  token = "another32bytehexdump"
 8:  postURL = 'https://mastodon.cloud/api/v1/statuses'
 9:  
10:  # The snowman prefix.
11:  # Currently, Mastodon's website messes up the display of the
12:  # snowman emoji, ⛄️, because the variation selector, U+FE0F,
13:  # appears as a box. This code constructs a string for the snowman
14:  # without the variation selector. It's possible this won't display
15:  # properly on some clients or platforms.
16:  prefix = b'\xe2\x9b\x84'.decode()
17:  
18:  # Assemble the toot content.
19:  home = environ['HOME']
20:  lastPostInfo = open(home + '/path/to/all-posts.txt').readlines()[-1]
21:  (date, time, slug, title) = lastPostInfo.split(' ', 3)
22:  title = title.rstrip()
23:  path = date[:-3].replace('-', '/')
24:  leanURL = 'http://leancrew.com/all-this/{}/{}/'.format(path, slug)
25:  toot = '''{} {}
26:  {}'''.format(prefix, title, leanURL)
27:  
28:  # Send the toot.
29:  header = {'Authorization': 'Bearer {}'.format(token)}
30:  payload = {'status': toot}
31:  r = requests.post(postURL, data=payload, headers=header)
32:  
33:  print(r.json()['uri'])

Every time I publish a new post here, my publishing system adds a new line to a file called all-posts.txt. Each line of the file looks like this:

2018-08-19 09:42:11 message-image-to-family Message Image to Family

The date and time of publication are the first two items. Then comes the “slug,” which is a URL-friendly transformation of the title. After the slug comes the post title itself.

Lines 18–26 get the last line of all-posts.txt and construct the content of the toot from it. For the post shown above, that content would be

⛄️ Message Image to Family
http://leancrew.com/all-this/2018/08/message-image-to-family

Putting the snowman emoji into the toot turned out to be trickier than it should have been. When I first wrote the script, Lines 25–26 looked like this:

python:
25:  toot = '''{} {}
26:  {}'''.format('⛄️', title, leanURL)

This, I soon learned, led to display problems when viewing the toot on the website in a browser. The snowman emoji came with a trailing box.

Snowman with trailing box

This didn’t seem to happen in Mastodon client apps that friends and I tried, and it was clearly a bug, but it was a bug that I had to work around. Lots of people access Mastodon through their browser, and I couldn’t have my toots looking like that.

Jason Biatek put me on the trail to a solution. When I use the emoji picker to add the snowman to my code, it puts a special Unicode character, Variation Selector-16, after the snowman. This is supposed be a nonprinting character that says “display the preceding character as an emoji instead of as a monochrome glyph.” It’s certainly displaying the emoji, but it’s screwing up the “nonprinting” part.

Line 16 is my workaround. After some playing around with various encode and decode commands in Python’s interactive mode, I came up with the three-byte string for the snowman emoji without the variation selector. And I wrote a long comment so future me would understand why I’m using bytes to handle what should be a simple string.

The toot gets sent on Lines 29–31 using Kenneth Reitz’s wonderful requests library. Line 29 constructs the header with the authorization token from Line 7. Line 30 constructs the data to be POSTed. And Line 31 does the posting to my instance’s API, using the URL from Line 8.

Finally, Line 33 takes the JSON output returned from the API call, extracts the uri, and prints it. This is the URL of the toot just sent.

Mastodon’s API docs are pretty good, but I couldn’t find any single place that had both steps of the app creation/authorization procedure. Now I can.


  1. I am not thrilled with “toot” as Mastodon’s answer to “tweet.” Likewise, I don’t think “instance” is a more friendly term than “server.” Many of Mastodon’s decisions seem odd, especially if you’ve spent a long time on Twitter, but it’s not nearly as confusing as some would have you believe. The best introduction to the practical aspects of going from Twitter to Mastodon is Joe Steel’s