diff options
author | Hubert Chathi <hubert@uhoreg.ca> | 2019-10-09 22:08:37 -0400 |
---|---|---|
committer | Hubert Chathi <hubert@uhoreg.ca> | 2019-10-09 22:12:17 -0400 |
commit | 815a57f3397fd3503b15d6db04bc8f8ea150236d (patch) | |
tree | c5367057c957ed2e3c51e4d8ea7a1b349a3d548f | |
parent | make ETS table public so that the sync process can write to it (diff) |
add a tutorial
-rw-r--r-- | README.md | 5 | ||||
-rw-r--r-- | mix.exs | 5 | ||||
-rw-r--r-- | tutorial_echo.md | 108 |
3 files changed, 117 insertions, 1 deletions
@@ -14,3 +14,8 @@ def deps do ] end ``` + +## Usage + +To use this library, start by looking at the the +[tutorial](tutorial_echo.html), or the documentation for `Polyjuice.Client`. @@ -19,7 +19,10 @@ defmodule PolyjuiceClient.MixProject do # The main page in the docs main: "readme", # logo: "path/to/logo.png", - extras: ["README.md"] + extras: [ + "README.md", + "tutorial_echo.md" + ] ], package: [ maintainers: ["Hubert Chathi"], 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. |