Integrating Huginn with a Matrix server.

Jan 19 2020

Throughout this series I've shown you how to set up a Matrix server and client using Synapse and Riot, and make it much more robust as a service by integrating a database server and a mechanism for making VoIP more reliable.  Now we'll wrap it up by doing something neat, building a simple agent network in Huginn to post what I'm listening to into a Matrix Room.  I have an account on libre.fm that my media players log to which we'll be using as our data source.  Of course, this is only a demonstration of the basic technique, you can, in theory plug whatever you want into a Matrix server because the API was designed to be extensible.

We're going to assume that you've already set up a Matrix server and have an account on it, and that you have access to a working Huginn install.

Let's start with creating a room.  Log into your server with Riot and click the + button next to "ROOMS" on the room management bar.  Call the channel "Now Playing" and enter a topic into the appropriate field.  Click the switch next to "Make this room public" so that people can opt into joining the channel if they want.  In the field that just appeared enter a unique name on your Matrix server for your new channel; Riot will tell you if it's available, and it should be because you're working on a new server.  Click on "Show advanced" and then click the switch next to "Block users on other matrix homeservers from joining this room."  We're setting this because we don't necessarily want anyone else to join this room.  This is, after all, an experiment so best practice would be to keep it sandboxed.  Your "Create room" panel should look like this.  Click the "Create Room" button.

We need two pieces of information: The internal channel ID for the room you've just created and your Matrix client's personal access token.  For reusability and manageability I strongly recommend saving them as credentials in Huginn.  The internal channel ID can be extracted by clicking on the three-dots menu next to your channel name and then selecting Settings.  If you want to you can upload an avatar for your room, but this is an experiment so don't worry about it.  This bit you want is in the room's advanced settings, under Room Information.  The room ID starts with a bang (!) and consists of eighteen alphanumeric characters, a colon (:), and then the hostname of the Matrix server.  Highlight this string and copy it.  In Huginn, click Credentials in the menu bar at the top, and then scroll down to the bottom.  Click the "New Credential" button.  Give the credential name "matrix_room_id" (descriptive is always better), leave the Mode set to "text" (which basically means "this is just data, don't worry about it"), and paste the internal room ID.  Click the "Save Credential" button.

This is not sensitive information, incidentally.  For Matrix servers to federate with each other the internal room ID has to be publically accessible.  For example, the public Matrix chat channel (#matrix:matrix.org) has the internal room ID "!OGEhHVWSdvArJzumhm:matrix.org", which you can get from any federated Matrix server.

The sensitive information we need, however, is your personal Matrix access token.  If someone has this token they can do things as you, and if you have an admin account you're in for a world of hurt.  So, treat this token the way you'd treat your password.  To get it click on the drop-down arrow next to your name, Settings.  Click on "Help & About" and scroll down to "Advanced."  Click on "<click to reveal>" next to "Access Token."  It doesn't look like a link but it really is.  You should see a block of what looks like garbage that's already highlighted for you.  Right-click -> Copy or control-c to copy it.  Go back to Huginn's Credentials screen and create a new entry called "matrix_access_token".  Again, leave the Mode set to "text" and paste your access token.  Hit "Save Credential."

The Huginn agent network has only two members: An agent to get the most recent tracks scrobbled, and an agent to do the actual posting to Matrix.  The nice thing about libre.fm's API is that you don't need an account or API key to get public information.  So, we're going to use a Website Agent to hit that API rail and get some information about the last couple of tracks I've listened to, namely, the name of the artist, the title of the track and album it's from, and a URL on libre.fm to get more information if you want it.  The agent looks like this:

{
  "expected_update_period_in_days": "365",
  "url": "https://libre.fm/2.0/?method=user.getrecenttracks&user=virtadpt&page=1&format=json",
  "type": "json",
  "mode": "on_change",
  "extract": {
    "artist": {
      "path": "$['recenttracks']['track'][*]['artist']['#text']"
    },
    "track": {
      "path": "$['recenttracks']['track'][*]['name']"
    },
    "album": {
      "path": "$['recenttracks']['track'][*]['album']['#text']"
    },
    "url": {
      "path": "$['recenttracks']['track'][*]['url']"
    }
  }
}

The JSONpaths look that way because libre.fm uses a less common formatting variant for JSON that's not well implemented and so the usual dotted paths don't work.  I had to fall back on bracket notation to make it work.

The second agent is a Post Agent that hits the Matrix API and posts the track information as a message to the room.  The agent looks like this, and then I'll explain how it works:

{
  "post_url": "https://matrix.jackpoint.virtadpt.net/_matrix/client/r0/rooms/{% credential matrix_room_id %}/send/m.room.message?access_token={% credential matrix_access_token %}",
  "expected_receive_period_in_days": "365",
  "content_type": "json",
  "method": "post",
  "payload": {
    "msgtype": "m.text",
    "body": "Artist: {{ artist }}\nTrack: {{ track }}\nAlbum: {{ album }}\nURL: {{ url }}"
  },
  "emit_events": "false",
  "no_merge": "false",
  "output_mode": "clean"
}

/_matrix/client/r0/rooms is the part of the API that has to do with interacting with Matrix rooms.  {% credential matrix_room_id %} tells Huginn "put the value of matrix_room_id here."  /send/m.room.message means pretty much what you'd expect - "send the payload of this request as a message to the specified room."  The payload of the request is the message itself.  "m.text" is the message type (it's just text).  "body" is a nicely formatted message about the song.  Every time the second agent receives an event, it'll execute, hence the lack of a schedule.  And in your channel you should see a couple of songs.

Now, this is only a toy example, a very simple network that demonstrates the most common interaction with the Matrix network.  It seems to me that this gives enough of the basic principle to build other agent networks on top of.  As before I've added sample user_credentials.json and scenario exports to my Github repository.  Import them into Huginn, edit them to reference your Matrix server (or the one you hang out on), and you're ready to go.  Good luck.