summaryrefslogtreecommitdiff
path: root/tutorial_echo.md
diff options
context:
space:
mode:
authorHubert Chathi <hubert@uhoreg.ca>2019-10-09 22:08:37 -0400
committerHubert Chathi <hubert@uhoreg.ca>2019-10-09 22:12:17 -0400
commit815a57f3397fd3503b15d6db04bc8f8ea150236d (patch)
treec5367057c957ed2e3c51e4d8ea7a1b349a3d548f /tutorial_echo.md
parentmake ETS table public so that the sync process can write to it (diff)
add a tutorial
Diffstat (limited to 'tutorial_echo.md')
-rw-r--r--tutorial_echo.md108
1 files changed, 108 insertions, 0 deletions
diff --git a/tutorial_echo.md b/tutorial_echo.md
new file mode 100644
index 0000000..d6d5ed8
--- /dev/null
+++ b/tutorial_echo.md
@@ -0,0 +1,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.