A Quick and Dirty Tutorial for Writing Mastodon Bots in Ruby
My most recent project has been this Mastodon bot. Every n hours (where n == 8, for now) it toots an interesting section of the Mandelbrot Set.
Unfortunately, when getting to the actual bot part of the project, I found very little in the way of tutorials or examples in Ruby, so I'm writing down what I did while it's still fresh in my mind.
1. Create an Account
First, we need an account on a Mastodon instance. I'm using http://botsin.space, an instance for bot writers. This is pretty straightfoward: just fill in your details in the signup form and away you go.
2. Get Credentials
Getting credentials is an overly complicated process that (fortunately) there are tools to help with. Basically you:
Register your application on the Mastodon instance. This gives you two big numbers (the client ID and client secret).
Using these numbers plus the username and password of your bot's account, log in. This gives you your bot's access token. Hold on to this.
From now on, the bot will use the access token to authenticate itself. You can invalidate the access token at any time from the bot account's settings (Settings -> Authorized Apps).
Unfortunately, the Ruby Mastodon client API library doesn't have an
easy way to do this for you. After wrestling with it for a while, I
ended up just using Darius Kazemi's registration app at
https://tinysubversions.com/notes/mastodon-bot/. It's a nice little
webapp that does everything reasonably securely. Just log into your
instance and, while logged in, go to the above URL and follow the
directions. (You do need to have curl
installed and available,
though.)
Failing that and if you have a Python installation handy, you can use the Python Mastodon module. Allison Parish's excellent guide for doing that is here.
3. Install the Mastodon API Gem
Assuming you have Ruby installed, it's a simple matter of typing
gem install mastodon-api
Unfortunately, as of this writing, there's a bug in the current release which has been fixed in development but which breaks the bot.
I worked around this by creating a Gemfile and listing a recent git
commit as the version. (Did you know that bundle
can install gems
from GitHub? It can!)
It looks like this:
source 'https://rubygems.org'
gem "mastodon-api", require: "mastodon", github: "tootsuite/mastodon-api",
branch: "master", ref: "a3ff60a"
Then, you cd
to the project directory and run bundle:
bundle
(Or, if you want to install the gems locally and aren't using rvm
or
rbenv
, you can install the gems locally:
bundle install --path ~/gems/
But you knew that, right?)
4. Basic Tooting
We are now ready to write a simple bot. This is done in two steps.
First, we create the client object:
client = Mastodon::REST::Client.new(
base_url: 'https://botsin.space',
bearer_token: 'bdc1fe7113d7cb9ea0f5d25')
The bearer_token
argument is the token you got in step 2. (The real
one will be longer than the one in the example.)
Then, we post the toot:
client.create_status("test status!!!!!")
And that should do it.
Here's the complete script:
require 'rubygems'
require 'bundler/setup'
require 'mastodon'
client = Mastodon::REST::Client.new(
base_url: 'https://botsin.space',
bearer_token: 'bdc1fe7113d7cb9ea0f5d25')
client.create_status("test status!!!!!")
One thing: because it's using bundler, the script must either be run
from the directory containing the Gemfile or you will need to set the
BUNDLE_GEMFILE
environment variable to point to it.
I ended up doing the latter by writing a shell script to set things up and then launch the bot:
#!/bin/bash
botdir="$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)"
export BUNDLE_GEMFILE="$botdir/Gemfile"
ruby "$botdir/basic_toot.rb" "$@"
The tricky expression in the second non-empty line extracts the directory containing the shell script itself; it figures out the location of the Gemfile from that.
5. Tooting With Media
Going from here to attaching a picture is pretty simple.
You set up the client as before and then upload the picture with
upload_media()
:
form = HTTP::FormData::File.new("#{FILE}.png")
media = client.upload_media(form)
It's possible that you don't need to wrap your filename in a
HTTP::FormData::File
object (the API docs seem to imply that) but I
wasn't able to get that to work and anyway, this doesn't hurt.
The media
object you get back from the call to upload_media()
contains various bits of information about it including an ID. This
gets attached to the toot:
status = File.open("#{FILE}.txt") {|fh| fh.read} # Get the toot text
sr = client.create_status(status, nil, [media.id])
It's in an array because (of course) you can attach multiple images to a toot.
Here's the entire script:
require 'rubygems'
require 'bundler/setup'
require 'mastodon'
FILE = "media_dir/toot"
client = Mastodon::REST::Client.new(
base_url: 'https://botsin.space',
bearer_token: 'bdc1fe7113d7cb9ea0f5d25')
form = HTTP::FormData::File.new("#{FILE}.png")
media = client.upload_media(form)
status = File.open("#{FILE}.txt") {|fh| fh.read}
sr = client.create_status(status, nil, [media.id])
This is mostly I use, plus the whole image plotting stuff, some logging stuff and a bunch of safety and error detection stuff.
6. Auth Token Management
You will note that I store the auth token as a string literal. I do this to simplify the example code but in general, it's a Bad Idea; mixing your credentials with your source code is just asking for your login details to get published on the open Internet. Keep them far away from each other.
For my bot, I use a YAML file that sits in the bot's work directory (which is not part of the source tree). Other options might be to pass it as a command-line argument or store it in an environment variable.
Other Resources
- The docs for Mastodon::REST::API provide a good overview of the API module's capabilities.
- Allison Parish's Python Tutorial (mentioned above) was also very helpful.
- Twittodon is another bot written in Ruby. I found it very useful as an example.
# Posted 2017-12-15 03:46:21 UTC; last changed 2018-10-05 01:04:04 UTC