From 6c3f5e01daa5ce7212f1e48f5d678835f8e6cfd2 Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Wed, 9 Oct 2019 19:56:28 -0400 Subject: add some documentation and move room functions to a separate submodule --- lib/polyjuice/client.ex | 182 ++++++++++-------------------------- lib/polyjuice/client/room.ex | 151 ++++++++++++++++++++++++++++++ lib/polyjuice/client/storage.ex | 2 +- lib/polyjuice/client/sync.ex | 2 +- test/polyjuice/client/room_test.exs | 137 +++++++++++++++++++++++++++ test/polyjuice/client_test.exs | 129 ------------------------- 6 files changed, 338 insertions(+), 265 deletions(-) create mode 100644 lib/polyjuice/client/room.ex create mode 100644 test/polyjuice/client/room_test.exs delete mode 100644 test/polyjuice/client_test.exs diff --git a/lib/polyjuice/client.ex b/lib/polyjuice/client.ex index 0c60241..93b672b 100644 --- a/lib/polyjuice/client.ex +++ b/lib/polyjuice/client.ex @@ -15,12 +15,38 @@ defmodule Polyjuice.Client do @moduledoc """ Matrix client functions. + + The struct in this module, or any struct that implements the + `Polyjuice.Client.API` protocol, can be used to connect to a Matrix server + using the functions from submodules. + + - `Polyjuice.Client.Filter`: build filters for use with sync + - `Polyjuice.Client.Room`: interact with rooms, such as sending messages + - `Polyjuice.Client.MsgBuilder`: build message contents + + To sync with the homeserver, start a process using the child spec returned by + `Polyjuice.Client.API.sync_child_spec/3`. + """ require Logger + @typedoc """ + Matrix client data. + + - `base_url` (string): Required. The base URL for the homeserver. + - `access_token` (string): Required to call endpoints that require an + authenticated user. The user's access token. + - `user_id` (string): Required for some endpoints and for sync. The user's + Matrix ID. + - `storage` (`Polyjuice.Client.Storage`): Required for some endpoints and for + sync. Storage for the client. + + """ @type t :: %__MODULE__{ base_url: String.t(), - access_token: String.t() + access_token: String.t(), + user_id: String.t(), + storage: Polyjuice.Client.Storage.t() } @enforce_keys [:base_url] @@ -42,8 +68,10 @@ defmodule Polyjuice.Client do """ @doc """ - Call a Matrix client API. This is a lower-level function; generally, clients - will want to call one of the higher-level functions from `Polyjuice.Client`. + Call a Matrix client API. + + This is a lower-level function; generally, clients will want to call one of + the higher-level functions from `Polyjuice.Client`. """ @spec call( client_api :: Polyjuice.Client.API.t(), @@ -59,10 +87,26 @@ defmodule Polyjuice.Client do @doc """ Get the child spec for the sync process. + + `listener` will receive messages with the sync results. Messages include: + + - `{:connected}`: the process has connected to the homeserver + - `{:disconnected}`: the process has been disconnected from the homeserver + - `{:initial_sync_completed}`: the first sync has completed + - `{:limited, room_id, prev_batch}`: a room's timeline has been limited. + Previous messages can be fetched using the `prev_batch` + - `{:message, room_id, event}`: a message event has been received + - `{:state, room_id, event}`: a state event has been received + - `{:invite, room_id, inviter, invite_state}`: the user was invited to a room + - `{:left, room_id}`: the user left a room + + `opts` is a keyword list of options: + + - `filter:` (string or map) the filter to use with the sync """ @spec sync_child_spec( client_api :: Polyjuice.Client.API.t(), - listener :: pid() | fun(), + listener :: pid(), opts :: list() ) :: map() def sync_child_spec(client_api, listener, opts \\ []) @@ -110,134 +154,4 @@ defmodule Polyjuice.Client do Polyjuice.Client.Sync.child_spec([client, listener | opts]) end end - - @doc """ - Send a message to a room. - - `msg` can either be anything that implements the - `Polyjuice.Client.MsgBuilder.MsgData` protocol (which will be sent as an - `m.message`), or a map (which specifies the full message content). - - Examples: - - Polyjuice.Client.send_message(client, "text message", "!room_id") - - Polyjuice.Client.send_message( - client, - {"message with formatting", "message with formatting"}, - "!room_id" - ) - - Polyjuice.Client.send_message( - client, - ["Hello, ", Polyjuice.Client.MsgBuilder.mention("@world:example.com")], - "!room_id" - ) - - Polyjuice.Client.send_message( - client, - %{"msgtype" => "m.notice", "body" => "using full message content"}, - "!room_id" - ) - - """ - @spec send_message( - client_api :: Polyjuice.Client.API.t(), - msg :: map | Polyjuice.Client.MsgBuilder.MsgData.t(), - room :: String.t() - ) :: String.t() - def send_message(client_api, msg, room) when is_binary(room) do - cond do - Polyjuice.Client.MsgBuilder.MsgData.impl_for(msg) != nil -> - Polyjuice.Client.API.call( - client_api, - %Polyjuice.Client.Endpoint.PutRoomsSend{ - txn_id: Polyjuice.Client.API.transaction_id(client_api), - room: room, - event_type: "m.room.message", - message: Polyjuice.Client.MsgBuilder.to_message(msg) - } - ) - - is_map(msg) and not Map.has_key?(msg, :__struct__) -> - Polyjuice.Client.API.call( - client_api, - %Polyjuice.Client.Endpoint.PutRoomsSend{ - txn_id: Polyjuice.Client.API.transaction_id(client_api), - room: room, - event_type: "m.room.message", - message: msg - } - ) - - true -> - raise ArgumentError, message: "invalid argument msg" - end - end - - @doc """ - Send an event to a room. - """ - @spec send_event( - client_api :: Polyjuice.Client.API.t(), - event_type :: String.t(), - event :: map, - room :: String.t() - ) :: String.t() - def send_event(client_api, event_type, event, room) - when is_binary(event_type) and is_map(event) and is_binary(room) do - Polyjuice.Client.API.call( - client_api, - %Polyjuice.Client.Endpoint.PutRoomsSend{ - txn_id: Polyjuice.Client.API.transaction_id(client_api), - room: room, - event_type: event_type, - message: event - } - ) - end - - @doc """ - Update the client's read receipt (of the given type) to the given message in the - given room. - """ - @spec update_read_receipt( - client_api :: Polyjuice.Client.API.t(), - room :: String.t(), - event_id :: String.t(), - receipt_type :: String.t() - ) :: Any - def update_read_receipt(client_api, room, event_id, receipt_type \\ "m.read") - when is_binary(room) and is_binary(event_id) and is_binary(receipt_type) do - Polyjuice.Client.API.call( - client_api, - %Polyjuice.Client.Endpoint.PostRoomsReceipt{ - room: room, - event_id: event_id, - receipt_type: receipt_type - } - ) - end - - @doc """ - Join a room. - """ - @spec join_room( - client_api :: Polyjuice.Client.API.t(), - room :: String.t(), - servers :: list(String.t()), - third_party_join :: map | nil - ) :: Any - def join_room(client_api, room, servers \\ [], third_party_signed \\ nil) - when is_binary(room) and is_list(servers) and - (is_map(third_party_signed) or third_party_signed == nil) do - Polyjuice.Client.API.call( - client_api, - %Polyjuice.Client.Endpoint.PostJoin{ - room: room, - servers: servers, - third_party_signed: third_party_signed - } - ) - end end diff --git a/lib/polyjuice/client/room.ex b/lib/polyjuice/client/room.ex new file mode 100644 index 0000000..f93bfd6 --- /dev/null +++ b/lib/polyjuice/client/room.ex @@ -0,0 +1,151 @@ +# Copyright 2019 Hubert Chathi +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +defmodule Polyjuice.Client.Room do + @moduledoc """ + Room-related functions. + + """ + require Logger + + @doc """ + Send a message to a room. + + `msg` can either be anything that implements the + `Polyjuice.Client.MsgBuilder.MsgData` protocol (which will be sent as an + `m.message`), or a map (which specifies the full message content). + + Examples: + + Polyjuice.Client.Room.send_message(client, "text message", "!room_id") + + Polyjuice.Client.Room.send_message( + client, + {"message with formatting", "message with formatting"}, + "!room_id" + ) + + Polyjuice.Client.Room.send_message( + client, + ["Hello, ", Polyjuice.Client.MsgBuilder.mention("@world:example.com")], + "!room_id" + ) + + Polyjuice.Client.Room.send_message( + client, + %{"msgtype" => "m.notice", "body" => "using full message content"}, + "!room_id" + ) + + """ + @spec send_message( + client_api :: Polyjuice.Client.API.t(), + room :: String.t(), + msg :: map | Polyjuice.Client.MsgBuilder.MsgData.t() + ) :: String.t() + def send_message(client_api, room, msg) when is_binary(room) do + cond do + Polyjuice.Client.MsgBuilder.MsgData.impl_for(msg) != nil -> + Polyjuice.Client.API.call( + client_api, + %Polyjuice.Client.Endpoint.PutRoomsSend{ + txn_id: Polyjuice.Client.API.transaction_id(client_api), + room: room, + event_type: "m.room.message", + message: Polyjuice.Client.MsgBuilder.to_message(msg) + } + ) + + is_map(msg) and not Map.has_key?(msg, :__struct__) -> + Polyjuice.Client.API.call( + client_api, + %Polyjuice.Client.Endpoint.PutRoomsSend{ + txn_id: Polyjuice.Client.API.transaction_id(client_api), + room: room, + event_type: "m.room.message", + message: msg + } + ) + + true -> + raise ArgumentError, message: "invalid argument msg" + end + end + + @doc """ + Send an event to a room. + """ + @spec send_event( + client_api :: Polyjuice.Client.API.t(), + room :: String.t(), + event_type :: String.t(), + event :: map + ) :: String.t() + def send_event(client_api, room, event_type, event) + when is_binary(event_type) and is_map(event) and is_binary(room) do + Polyjuice.Client.API.call( + client_api, + %Polyjuice.Client.Endpoint.PutRoomsSend{ + txn_id: Polyjuice.Client.API.transaction_id(client_api), + room: room, + event_type: event_type, + message: event + } + ) + end + + @doc """ + Update the client's read receipt (of the given type) to the given message in the + given room. + """ + @spec update_read_receipt( + client_api :: Polyjuice.Client.API.t(), + room :: String.t(), + event_id :: String.t(), + receipt_type :: String.t() + ) :: Any + def update_read_receipt(client_api, room, event_id, receipt_type \\ "m.read") + when is_binary(room) and is_binary(event_id) and is_binary(receipt_type) do + Polyjuice.Client.API.call( + client_api, + %Polyjuice.Client.Endpoint.PostRoomsReceipt{ + room: room, + event_id: event_id, + receipt_type: receipt_type + } + ) + end + + @doc """ + Join a room. + """ + @spec join( + client_api :: Polyjuice.Client.API.t(), + room :: String.t(), + servers :: list(String.t()), + third_party_join :: map | nil + ) :: Any + def join(client_api, room, servers \\ [], third_party_signed \\ nil) + when is_binary(room) and is_list(servers) and + (is_map(third_party_signed) or third_party_signed == nil) do + Polyjuice.Client.API.call( + client_api, + %Polyjuice.Client.Endpoint.PostJoin{ + room: room, + servers: servers, + third_party_signed: third_party_signed + } + ) + end +end diff --git a/lib/polyjuice/client/storage.ex b/lib/polyjuice/client/storage.ex index 3f5e26d..9172e5b 100644 --- a/lib/polyjuice/client/storage.ex +++ b/lib/polyjuice/client/storage.ex @@ -55,7 +55,7 @@ defprotocol Polyjuice.Client.Storage do def set_filter_id(storage, filter, filter_id) @doc """ - Get the ID stored for a filter, or `nil' if no ID has been stored. + Get the ID stored for a filter, or `nil` if no ID has been stored. """ @spec get_filter_id(storage :: __MODULE__.t(), filter :: map) :: String.t() | nil def get_filter_id(storage, filter) diff --git a/lib/polyjuice/client/sync.ex b/lib/polyjuice/client/sync.ex index 4bd1a72..70f357c 100644 --- a/lib/polyjuice/client/sync.ex +++ b/lib/polyjuice/client/sync.ex @@ -302,7 +302,7 @@ defmodule Polyjuice.Client.Sync do if Map.get(timeline, "limited", false) do with {:ok, prev_batch} <- Map.get(timeline, "prev_batch") do - send(state.listener, {:limited, room, prev_batch}) + send(state.listener, {:limited, roomname, prev_batch}) end end diff --git a/test/polyjuice/client/room_test.exs b/test/polyjuice/client/room_test.exs new file mode 100644 index 0000000..36d80cb --- /dev/null +++ b/test/polyjuice/client/room_test.exs @@ -0,0 +1,137 @@ +# Copyright 2019 Hubert Chathi +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +defmodule Polyjuice.Client.RoomTest do + use ExUnit.Case + doctest Polyjuice.Client.Room + + test "send message" do + with client = %DummyClient{ + response: { + %Polyjuice.Client.Endpoint.PutRoomsSend{ + room: "!bar", + txn_id: "txn_id", + event_type: "m.room.message", + message: %{ + "msgtype" => "m.text", + "body" => "foo" + } + }, + {:ok, "$foo1"} + } + } do + {:ok, event_id} = Polyjuice.Client.Room.send_message(client, "!bar", "foo") + assert event_id == "$foo1" + end + + with client = %DummyClient{ + response: { + %Polyjuice.Client.Endpoint.PutRoomsSend{ + room: "!bar", + txn_id: "txn_id", + event_type: "m.room.message", + message: %{ + "msgtype" => "m.text", + "formatted_body" => "foo", + "format" => "org.matrix.custom.html", + "body" => "foo" + } + }, + {:ok, "$foo2"} + } + } do + {:ok, event_id} = Polyjuice.Client.Room.send_message(client, "!bar", {"foo", "foo"}) + assert event_id == "$foo2" + end + + with client = %DummyClient{ + response: { + %Polyjuice.Client.Endpoint.PutRoomsSend{ + room: "!bar", + txn_id: "txn_id", + event_type: "m.room.message", + message: %{ + "msgtype" => "m.notice", + "body" => "foo" + } + }, + {:ok, "$foo3"} + } + } do + {:ok, event_id} = + Polyjuice.Client.Room.send_message(client, "!bar", %{ + "msgtype" => "m.notice", + "body" => "foo" + }) + + assert event_id == "$foo3" + + # trying to send a non-msgdata should error + assert_raise ArgumentError, fn -> + Polyjuice.Client.Room.send_message(client, "!bar", 1) + end + + assert_raise ArgumentError, fn -> + Polyjuice.Client.Room.send_message(client, "!bar", client) + end + + assert_raise FunctionClauseError, fn -> + Polyjuice.Client.Room.send_message(client, "!bar", {"a"}) + end + end + end + + test "send event" do + with client = %DummyClient{ + response: { + %Polyjuice.Client.Endpoint.PutRoomsSend{ + room: "!bar", + txn_id: "txn_id", + event_type: "m.room.message", + message: %{ + "msgtype" => "m.text", + "body" => "foo" + } + }, + {:ok, "$foo1"} + } + } do + {:ok, event_id} = + Polyjuice.Client.Room.send_event( + client, + "!bar", + "m.room.message", + %{"msgtype" => "m.text", "body" => "foo"} + ) + + assert event_id == "$foo1" + end + end + + test "update read receipt" do + with client = %DummyClient{ + response: { + %Polyjuice.Client.Endpoint.PostRoomsReceipt{ + room: "!room", + event_id: "$event", + receipt_type: "m.read" + }, + {:ok} + } + } do + {:ok} = Polyjuice.Client.Room.update_read_receipt(client, "!room", "$event", "m.read") + {:ok} = Polyjuice.Client.Room.update_read_receipt(client, "!room", "$event") + end + end +end diff --git a/test/polyjuice/client_test.exs b/test/polyjuice/client_test.exs deleted file mode 100644 index 7c25bc4..0000000 --- a/test/polyjuice/client_test.exs +++ /dev/null @@ -1,129 +0,0 @@ -# Copyright 2019 Hubert Chathi -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -defmodule Polyjuice.ClientTest do - use ExUnit.Case - doctest Polyjuice.Client - - test "send message" do - with client = %DummyClient{ - response: { - %Polyjuice.Client.Endpoint.PutRoomsSend{ - room: "!bar", - txn_id: "txn_id", - event_type: "m.room.message", - message: %{ - "msgtype" => "m.text", - "body" => "foo" - } - }, - {:ok, "$foo1"} - } - } do - {:ok, event_id} = Polyjuice.Client.send_message(client, "foo", "!bar") - assert event_id == "$foo1" - end - - with client = %DummyClient{ - response: { - %Polyjuice.Client.Endpoint.PutRoomsSend{ - room: "!bar", - txn_id: "txn_id", - event_type: "m.room.message", - message: %{ - "msgtype" => "m.text", - "formatted_body" => "foo", - "format" => "org.matrix.custom.html", - "body" => "foo" - } - }, - {:ok, "$foo2"} - } - } do - {:ok, event_id} = Polyjuice.Client.send_message(client, {"foo", "foo"}, "!bar") - assert event_id == "$foo2" - end - - with client = %DummyClient{ - response: { - %Polyjuice.Client.Endpoint.PutRoomsSend{ - room: "!bar", - txn_id: "txn_id", - event_type: "m.room.message", - message: %{ - "msgtype" => "m.notice", - "body" => "foo" - } - }, - {:ok, "$foo3"} - } - } do - {:ok, event_id} = - Polyjuice.Client.send_message(client, %{"msgtype" => "m.notice", "body" => "foo"}, "!bar") - - assert event_id == "$foo3" - - # trying to send a non-msgdata should error - assert_raise ArgumentError, fn -> Polyjuice.Client.send_message(client, 1, "!bar") end - assert_raise ArgumentError, fn -> Polyjuice.Client.send_message(client, client, "!bar") end - - assert_raise FunctionClauseError, fn -> - Polyjuice.Client.send_message(client, {"a"}, "!bar") - end - end - end - - test "send event" do - with client = %DummyClient{ - response: { - %Polyjuice.Client.Endpoint.PutRoomsSend{ - room: "!bar", - txn_id: "txn_id", - event_type: "m.room.message", - message: %{ - "msgtype" => "m.text", - "body" => "foo" - } - }, - {:ok, "$foo1"} - } - } do - {:ok, event_id} = - Polyjuice.Client.send_event( - client, - "m.room.message", - %{"msgtype" => "m.text", "body" => "foo"}, - "!bar" - ) - - assert event_id == "$foo1" - end - end - - test "update read receipt" do - with client = %DummyClient{ - response: { - %Polyjuice.Client.Endpoint.PostRoomsReceipt{ - room: "!room", - event_id: "$event", - receipt_type: "m.read" - }, - {:ok} - } - } do - {:ok} = Polyjuice.Client.update_read_receipt(client, "!room", "$event", "m.read") - {:ok} = Polyjuice.Client.update_read_receipt(client, "!room", "$event") - end - end -end -- cgit v1.2.3