# 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
website=http://leancrew.com

• The client name is arbitrary, but some Mastodon client apps will show it, so don’t choose something that will embarrass you.
• The weird redirect URI is what the API docs say you should use if you don’t want the user to be redirected after authorization. For single purpose apps like mine, this is the correct choice.
• The script I’m going to build is for writing toots, but I might use the same app registration for other things, so I added read, too.
• The app’s website is here.

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

• The client_id and client_secret came from the previous step.
• The scope is the same as before. Why do we need to repeat this? I assume it’s because app users may not want to authorize all the scopes an app can deliver. More mysterious to me is why this time it’s called “scope” when the last time it was called “scopes.”
• The grant_type means we’re using a password to authorize the app.
• The username and password are mine.

The output of this command looked like

{"access_token":"another32bytehexdump",
"token_type":"Bearer",
"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)}
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.

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