diff options
-rw-r--r-- | lib/polyjuice/client.ex | 182 | ||||
-rw-r--r-- | lib/polyjuice/client/room.ex | 151 | ||||
-rw-r--r-- | lib/polyjuice/client/storage.ex | 2 | ||||
-rw-r--r-- | lib/polyjuice/client/sync.ex | 2 | ||||
-rw-r--r-- | test/polyjuice/client/room_test.exs (renamed from test/polyjuice/client_test.exs) | 34 |
5 files changed, 222 insertions, 149 deletions
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", "<i>message</i> with <b>formatting</b>"}, - "!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 <hubert@uhoreg.ca> +# +# 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", "<i>message</i> with <b>formatting</b>"}, + "!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_test.exs b/test/polyjuice/client/room_test.exs index 7c25bc4..36d80cb 100644 --- a/test/polyjuice/client_test.exs +++ b/test/polyjuice/client/room_test.exs @@ -12,9 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -defmodule Polyjuice.ClientTest do +defmodule Polyjuice.Client.RoomTest do use ExUnit.Case - doctest Polyjuice.Client + doctest Polyjuice.Client.Room test "send message" do with client = %DummyClient{ @@ -31,7 +31,7 @@ defmodule Polyjuice.ClientTest do {:ok, "$foo1"} } } do - {:ok, event_id} = Polyjuice.Client.send_message(client, "foo", "!bar") + {:ok, event_id} = Polyjuice.Client.Room.send_message(client, "!bar", "foo") assert event_id == "$foo1" end @@ -51,7 +51,7 @@ defmodule Polyjuice.ClientTest do {:ok, "$foo2"} } } do - {:ok, event_id} = Polyjuice.Client.send_message(client, {"foo", "<i>foo</i>"}, "!bar") + {:ok, event_id} = Polyjuice.Client.Room.send_message(client, "!bar", {"foo", "<i>foo</i>"}) assert event_id == "$foo2" end @@ -70,16 +70,24 @@ defmodule Polyjuice.ClientTest do } } do {:ok, event_id} = - Polyjuice.Client.send_message(client, %{"msgtype" => "m.notice", "body" => "foo"}, "!bar") + 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.send_message(client, 1, "!bar") end - assert_raise ArgumentError, fn -> Polyjuice.Client.send_message(client, client, "!bar") end + 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.send_message(client, {"a"}, "!bar") + Polyjuice.Client.Room.send_message(client, "!bar", {"a"}) end end end @@ -100,11 +108,11 @@ defmodule Polyjuice.ClientTest do } } do {:ok, event_id} = - Polyjuice.Client.send_event( + Polyjuice.Client.Room.send_event( client, + "!bar", "m.room.message", - %{"msgtype" => "m.text", "body" => "foo"}, - "!bar" + %{"msgtype" => "m.text", "body" => "foo"} ) assert event_id == "$foo1" @@ -122,8 +130,8 @@ defmodule Polyjuice.ClientTest do {:ok} } } do - {:ok} = Polyjuice.Client.update_read_receipt(client, "!room", "$event", "m.read") - {:ok} = Polyjuice.Client.update_read_receipt(client, "!room", "$event") + {: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 |