Interfacing Huginn with Mastodon.

Aug 18 2018

It seems that there is another influx of refugees from a certain social network that's turned into a never ending flood of bile, vitriol, and cortisol into what we call the Fediverse, a network of a couple of thousand websites running a number of different applications that communicate with each other over a protocol called ActivityPub.  Ultimately, the Fediverse is different from Twitter and Facebook in that it's not run as a for-profit entity. There are no analytics, no suggestions of "thought leaders" you might want to follow, no automated curation of the posts you can see versus the ones you really want to see.  Socially speaking, you don't find people carefully polishing their brands or trying to game hashtag trends but instead everything from somebody kicking back after work with a cup of coffee to people carefully archiving the firmware of classic computer hardware to in-jokes about pineapples.  Rather than fame, you get people.

But that's not what I want to talk about.  I've been asked by a couple of people to post a brief tutorial of how I interfaced my Huginn instance with mastodon.social, the Mastodon instance that I spend most of my time hanging out on.

First of all, you need to have an account on one of the many Mastodon instances out there.  I recommend going through the questionaire at instances.social to pick the instance you want to call home.  Create an account and make yourself at home; at the very least set up your profile - here's mine, for example.

Now you need to get an API key for your home Mastodon instance, whichever one it happens to be.  If your home instance is running a newer build of the Mastodon software, click the "Edit profile" link beneath your name, scroll down to and click on the link for "Development."  Click the "New Application" button.  For the purposes of this demonstration, just make sure that "read" and "write" are checked, and "follow" is unchecked, like this.  Click the "Submit" button.  You should see the message "Application successfully created."  Click on the name of your application.

What you will see on the screen is a set of three strings of what look like garbage or line noise, labeled "Client key," "Client secret," and "Your access token."  The third, the access token is the one you want so highlight it with your curser and hit control+c to copy it.

Now head over to your Huginn instance, log in, and click on "Credentials" in the bar at the top of the window.  This is where you're going to store the Mastodon access token you just created.  Click on "+ New Credential" at the bottom of that window.  In the "Credential name" field, type in the name mastodon_access_token.  Leave the "Mode" field set to Text.  Paste the Mastodon accss token you copied earlier into "Credential value" field.  Click the "Save credential" button.  Your credential list should look something like this.

You are now half done.  It's time to construct a simple agent network in Huginn that talks to Mastodon.

In Huginn, click "Scenarios" in the menu bar at the top.  Scroll down and click the "New Scenario" button.  Enter a name for your new scenario - I called it "Mastodon Integration Demo."  Give it a description if you want, pick a color scheme and icon for it if you want, and then click the "Save Scenario" button.Now, this demo is going to be very simple.  There will be a Manual Event Agent to accept arbitrary text from you and a Post Agent which communicates with Mastodon.  Click "Agents" in the menu bar at the top of your Huginn install.  In the "Type" field, click and scroll down until you find the Manual Event Agent.  Give the agent a name; I used "Manually inject events into the Mastodon agent" for simplicity's sake.  Set the value of "Keep events" to "1 hour" so that the events the agent creates will be deleted after an hour.  In the "Scenarios" field click and scroll until you find the name of the scenario you created (e.g., "Mastodon Integration Demo").  Click the "Save" button to save the agent.

Click "Agents" at the top again and this time choose a Post Agent.  Give the agent a descriptive name like "Send event to Mastodon."  Set the Schedule field to "Never," meaning that it'll only run when it receives an event from someplace else.  Set "Keep events" to "1 hour" as before.  In the "Sources" field, select the name of the Manual Event Agent you just created.  Set the "Scenarios" field to the name of the scenario you created (e.g., "Mastodon Integration Demo").  In the "Options" window click "Toggle View" and enter the following code, which I'll explain shortly:

{
  "post_url": "https://mastodon.social/api/v1/statuses",
  "expected_receive_period_in_days": "365",
  "content_type": "form",
  "method": "post",
  "payload": {
    "status": "{{ message }}"
  },
  "headers": {
    "Authorization": "Bearer {% credential mastodon_access_token %}"
  },
  "emit_events": "false",
  "no_merge": "false"
}

"post_url" is the full URL to your home Mastodon instance (here, mastodon.social but substitute the hostname of whatever instance you call home) with "/api/v1/statuses" appended to it.  This is the API call which enters a new post on your timeline.

"expected_receive_period_in_days" is set to 365, meaning that up to one calendar year can pass before the agent gets flagged as nonfunctional.

"content_type" is set to "form" because that is the kind of HTTP request payload the Mastodon API expects.

"method" is set to "post" because that is the kind of HTTP request the Mastodon API expects when a new entry is created.  The specific reasons for this are long and involved and are derived from how REST APIs work.  I'm not going to go into it here but there are some very good resources out there which do a better job of explaining it than I can.

"payload" is where the stuff you're posting to Mastodon goes.  It expects a field called "status" and pulls the contents of a field called message out of any incoming events (that's what the {{ }} braces mean, though you'll see that the example code I give is a bit different; don't worry, it works just the same).

"headers" is the tricky bit because this is where you authenticate to Mastodon - where you prove your identity and permission to post something.  The name of the header is "Authorization", and the value of the header is "Bearer {% credential mastodon_access_token %}".  "Bearer" is the kind of authorization you're using - what it basically means is "Hey, this piece of garbage you generated for me?  Because I have it I'm allowed to do blah."  The Mastodon instance looks in its database for that bearer token, sees that it did indeed generate it, and essentially says "Sure, whatever you want to do as your account you can do."  In Huginn, you reference the value of a credential with the {% credential <name of credential here> %} tag, as documented here.

"emit_events" is set to "false", meaning that the Post Agent will not emit any events as it runs.

Click the "Save" button at the bottom.

Now, if you click the "View Diagram" button at the bottom you should see a network of two agents hooked together like this:

Let's test.  Click on the "Manually inject events..." agent.  Click on "Toggle View" and enter the following code in the "Options" window:

{
  "message": "Hello, Fediverse!"
}

Then click the "Submit" button.  Depending on how busy your Huginn instance is, it might take a couple of minutes for your request to go through because Huginn uses a first-in-first-out job queue.  But, as those two new agents execute you will soon see...

As you can see, the magick is in the Post Agent.  So, you could theoretically plug any other agent in that you wanted and, as long as the fields of the event the Post Agent receives are represented will be posted.  Say, an RSS Agent that checks on my blog twice a day:

{
  "expected_update_period_in_days": "365",
  "clean": "false",
  "url": "https://drwho.virtadpt.net/rss/feed.xml"
}

We'll have to make a minor tweak to the Post Agent that talks to Mastodon by editing the "payload" field:

...
  "payload": {
    "status": "{{ message }}{{ title }} {{ url }}"
  },
...

By convention, if a Huginn agent is told to look for a tag (like {{ message }}) but it doesn't exist in any incoming events, it'll ignore it entirely, which is to say that it'll substitute an empty string, "".  With a little forethought it's possible to use this technique to make multipurpose agents (though don't go overboard with this, it can make agent networks much harder to debug later).  Now our example agent network will accept {{ message }} events (it'll pretend {{ title }} and {{ url }} don't exist) and events that have both {{ title }} and {{ url }} tags in them.  Every time I update my blog, this demo agent will post the title and permalink to the post.  You probably don't want this in practice but I think it illustrates the principle nicely.

I think that just about covers it.  If you want to get more involved in using Huginn to interact with the Fediverse, I strongly recommend that you read through the Mastodon API documentation, which describes all of the API rails, how to interact with them, what they expect, and what they send back.  If you want to experiment with the small agent network I demonstrated above, I've exported it and checked it into my Github repo as mastodon-integration-demo.json.

Happy hacking!