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 | 
