Usage of matrix-js-bot-sdk

This article concerns matrix-js-bot-sdk, a TypeScript client SDK for Matrix. We'll build a simple "echo bot", meaning a bot which replies to messages with the text it has just read.

Note that although the SDK is written in TypeScript, we'll use JavaScript in our examples. If you'd prefer to use TypeScript, then do!

Setup

Let's make a new folder, and import our only npm dependency. The following examples are all meant to be run in a bash terminal.

mkdir matrix-js-echo-bot
cd matrix-js-echo-bot
npm install matrix-bot-sdk

Create a new file named "index.js", and let's get started.

Instantiation

In our js file, start by importing the minimum we'll need for this example:

const sdk = require("matrix-bot-sdk");
const MatrixClient = sdk.MatrixClient;
const SimpleFsStorageProvider = sdk.SimpleFsStorageProvider;
const AutojoinRoomsMixin = sdk.AutojoinRoomsMixin;

Create a new account for your bot on a homeserver, then get the access_token. The simplest way to do this is using Riot, take a look at these instructions. Set some variables to store the homeserver and access_token. This is all the authentication you need!

const homeserverUrl = "https://matrix.org"; // make sure to update this with your url
const accessToken = "YourSecretAccessToken";

New we'll configure a storage provider - matrix-js-bot-sdk provides the SimpleFsStorageProvider, which is ideal for most cases:

const storage = new SimpleFsStorageProvider("bot.json");

When the bot starts, the SDK will create a a new file called "bot.json" to store the data it needs.

Finally we're ready to start the client! As you'd expect, we'll use the variables we've already specified.

const client = new MatrixClient(homeserverUrl, accessToken, storage);

There is one more thing we need to do. We'll include a mixin which instructs the bot to auto-accept any room invite it receives. This makes testing much more convenient.

AutojoinRoomsMixin.setupOnClient(client);

Finally, let's start the Client:

client.start().then(() => console.log("Client started!"));

If you're keeping up, your code should look something like:

import {
    MatrixClient,
    SimpleFsStorageProvider,
    AutojoinRoomsMixin
} from "matrix-bot-sdk";

const homeserverUrl = "https://matrix.org"; // make sure to update this with your url
const accessToken = "YourSecretAccessToken";
const storage = new SimpleFsStorageProvider("bot.json");

const client = new MatrixClient(homeserverUrl, accessToken, storage);
AutojoinRoomsMixin.setupOnClient(client);

client.start().then(() => console.log("Client started!"));

Let's run it:

node index.js

This should now join and sit idle, but join any room you invite the bot to.

/sync loop

Right now, while it's just listening to invites and nothing else, what is the bot actually doing? It's calling the /sync endpoint in a loop. Calling this endpoint returns all new events since some previous point.

Leave the script running and open bot.json, which is the file we specified for storage. This file contains a field syncToken, which is being occasionally updated - the SDK uses this field to give a token to the homeserver, which uses it to know which events to send back.

Receiving and Sending events

In order to echo messages, our bot must first be able to read them. The client.on() method of our MatrixClient takes two arguments: one for the event type, one for a callback to handle the event:

client.on("room.message", (roomId, event) => {
    if (! event["content"]) return;
    const sender = event["sender"];
    const body = event["content"]["body"];
    console.log(`${roomId}: ${sender} says '${body}`);
});

In this way we can inspect an the contents of an event and render them. We choose to exit early in the case that event["content"] is empty because this will usually mean the message was redacted.

To send a message, we use the client.sendMessage() method. This takes two arguments: the roomId, and a JSON object containing the contents of the message to send, for example:

client.sendMessage(roomId, {
    "msgtype": "m.text",
    "body": "This is message text.",
});

Note, it's also possible to use client.sendText() to achieve the same result, as in

client.sendText(roomId, "This is message text.")

The reason for showing client.sendMessage() is to make it clear that the message format is just the same as you'd find in the spec.

Implementing echobot functionality

To work, an echobot needs only to listen for incoming messages, read the message text, and use it to reply. Let's demonstrate that now.

  • Read the message as in the example above
  • Inspect the body text, if it starts with "!echo", send back the remaining text
  • Strip out the "!echo" tag
  • Send a message containing the result
client.on("room.message", (roomId, event) => {
    if (! event["content"]) return;
    const sender = event["sender"];
    const body = event["content"]["body"];
    console.log(`${roomId}: ${sender} says '${body}`);

    if (body.startsWith("!echo")) {
        const replyText = body.substring("!echo".length).trim();
        client.sendMessage(roomId, {
            "msgtype": "m.notice",
            "body": replyText,
        });
    }
});

Conclusion

It's extremely simple to listen to messages with matrix-js-bot-sdk create an echobot! There are many more features, you can see the MatrixClient class is very well documented. In the next in this series we'll explore Rich Replies, and take a look at the kick and ban functions for room administration.

PS, use TypeScript

This SDK uses TypeScript, which provides a lot of benefits. In this example, we used JavaScript, but it's just as easy to use TypeScript and maybe preferable, since it is the language matrix-js-bot-sdk is written in.

First let's install tsc, which compiles from TypeScript to JavaScript:

npm install tsc

Now, start tsc in watch-mode (-w), and leave it to compile our code:

npx tsc --watch *.ts

Now, whenever we create a new TypeScript (.ts) file, it will be automatically watched and compiled to JavaScript.

When you have your .js file(s), you can run them with node <filename> as normal.