summaryrefslogtreecommitdiff
path: root/tutorial_echo.md
blob: d6d5ed893c86c24c91d03c23ccb3684c30236058 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
Tutorial: Echo bot
==================

In this tutorial, we will construct a simple echo bot: for every message that
the echo bot sees, it will repeat that message.  The commands in this tutorial
can be entered interactively into `iex -S mix` to produce a working bot.

## Creating a client

We start by creating a `Polyjuice.Client` struct:

    iex> client = %Polyjuice.Client{
    ...>   base_url: "http://localhost:8008",
    ...>   access_token: "access_token",
    ...>   user_id: "@echobot:localhost",
    ...>   storage: Polyjuice.Client.Storage.Ets.open()
    ...> }

The `base_url` parameter is the base URL to the homeserver that the bot will be
using.  `access_token` is an access token for the bot to use.  The access token
can be obtained by using `curl` to call the
[login](https://matrix.org/docs/spec/client_server/r0.5.0#post-matrix-client-r0-login)
endpoint on the homeserver, or by retrieving it from an already logged-in
client.  `user_id` is the Matrix user ID of the bot.  And `storage` provides
some persistent storage for the client library.  In the example above, it is
using the [Erlang Term Storage](http://erlang.org/doc/man/ets.html), which does
not actually provide any persistence.  This means that, for example, the bot
will forget where it was in the sync, and so it may produce duplicate responses
when it is restarted.  If you wish to have persistence, you can use the
`Polyjuice.Client.Storage.Dets` module instead, which will persist the term
storage to disk.  To use it, call `Polyjuice.Client.Storage.Dets.open/1` with
the name of a file to use for storage.

## Sync

Once the client has been created, a sync process can be started to receive
messages.  The sync can be started in a supervisor, so that if it dies, it will
be restarted.  Calling `Polyjuice.Client.API.sync_child_spec/3` will generate a
child spec that can be passed to `Supervisor.start_link`.  Its first argument
is the client struct that we created earlier, and its second argument will be a
`pid` that will receive messages.  In our case, we will send it to `self` since
we will handle the messages.  You can also pass in some options; we will omit
them for now.

    iex> children = [Polyjuice.Client.API.sync_child_spec(client, self)]
    iex> {:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one)

## Reacting to sync messages

The sync process will now start sending several different types of messages,
letting us know of many things such as the status of our connection to the
Matrix server, new Matrix messages, and state changes.  The messages that we
are most interested in are new Matrix messages (so that we can echo them), and
room invites (so that we can join).

The message for a Matrix message is of the form `{:message, room_id, event}`.
When we receive such a message, we can use the
`Polyjuice.Client.Room.send_message/3` function to respond to the message.  We
will use pattern matching to make sure to only respond to `m.text` messages,
and respond with a `m.notice` message, so that we don't create a message loop.
A `receive` statement to do this would look something like this:

    receive do
      {:message, room_id, %{"content" => %{"msgtype" => "m.text"} = content}} ->
        Polyjuice.Client.Room.send_message(
          client, room_id,
          %{content | "msgtype" => "m.notice"}
        )
    end

Room invites are of the form `{:invite, room_id, inviter, invite_state}`.  When
we get an invite, we can join the room using `Polyjuice.Client.Room.join/4`.
Although that function takes four arguments, the last two are optional, and are
not needed when responding to an invite.  A `receive` statement that joins a
room that we're invited to would look something like this:

    receive do
      {:invite, room_id, _inviter, _invite_state} ->
        Polyjuice.Client.Room.join(client, room_id)
    end

If you just enter one of the above, you'll only be able respond to one thing.
To be able to continuously respond tomessages, we put this all in a function
that calls itself recursively:

    iex> defmodule EchoBot do
    ...>   def loop(client) do
    ...>     receive do
    ...>       {:message, room_id, %{"content" => %{"msgtype" => "m.text"} = content}} ->
    ...>         Polyjuice.Client.Room.send_message(
    ...>           client, room_id,
    ...>           %{content | "msgtype" => "m.notice"}
    ...>         )
    ...>
    ...>       {:invite, room_id, _inviter, _invite_state} ->
    ...>         Polyjuice.Client.Room.join(client, room_id)
    ...>
    ...>       _ ->
    ...>         nil
    ...>     end
    ...>     loop(client)
    ...>   end
    ...> end
    iex> EchoBot.loop(client)

If you enter the above lines, then you can invite the bot into some rooms and
it will repeat any messages that it sees.  Note that some of the initial
messages may get dropped due to rate limiting from the homeserver.