diff options
Diffstat (limited to 'lib/lsg_matrix/room.ex')
-rw-r--r-- | lib/lsg_matrix/room.ex | 178 |
1 files changed, 178 insertions, 0 deletions
diff --git a/lib/lsg_matrix/room.ex b/lib/lsg_matrix/room.ex new file mode 100644 index 0000000..31d1b06 --- /dev/null +++ b/lib/lsg_matrix/room.ex @@ -0,0 +1,178 @@ +defmodule LSG.Matrix.Room do + require Logger + alias LSG.Matrix + alias Polyjuice.Client + import Matrix, only: [client: 0, client: 1, user_name: 1, myself?: 1] + + defmodule Supervisor do + use DynamicSupervisor + + def start_link() do + DynamicSupervisor.start_link(__MODULE__, [], name: __MODULE__) + end + + def start_child(room_id) do + spec = %{id: room_id, start: {LSG.Matrix.Room, :start_link, [room_id]}, restart: :transient} + DynamicSupervisor.start_child(__MODULE__, spec) + end + + @impl true + def init(_init_arg) do + DynamicSupervisor.init( + strategy: :one_for_one, + max_restarts: 10, + max_seconds: 1 + ) + end + end + + def start(room_id) do + __MODULE__.Supervisor.start_child(room_id) + end + + def start_link(room_id) do + GenServer.start_link(__MODULE__, [room_id], name: name(room_id)) + end + + def start_and_send_matrix_event(room_id, event) do + pid = if pid = whereis(room_id) do + pid + else + case __MODULE__.start(room_id) do + {:ok, pid} -> pid + {:error, {:already_started, pid}} -> pid + end + end + send(pid, {:matrix_event, event}) + end + + def whereis(room_id) do + {:global, name} = name(room_id) + case :global.whereis_name(name) do + :undefined -> nil + pid -> pid + end + end + + def name(room_id) do + {:global, {__MODULE__, room_id}} + end + + def init([room_id]) do + {:ok, state} = Matrix.lookup_room(room_id) + Logger.metadata(matrix_room: room_id) + + {:ok, _} = Registry.register(IRC.PubSub, "#{state.network}:events", plugin: __MODULE__) + for t <- ["messages", "triggers", "outputs", "events"] do + {:ok, _} = Registry.register(IRC.PubSub, "#{state.network}/#{state.channel}:#{t}", plugin: __MODULE__) + end + + state = state + |> Map.put(:id, room_id) + Logger.info("Started Matrix room #{room_id}") + {:ok, state, {:continue, :update_state}} + end + + def handle_continue(:update_state, state) do + {:ok, s} = Client.Room.get_state(client(), state.id) + members = Enum.reduce(s, [], fn(s, acc) -> + if s["type"] == "m.room.member" do + if s["content"]["membership"] == "join" do + [s["user_id"] | acc] + end + else + # XXX: The user left, remove from IRC.Memberships ? + acc + end + end) + for m <- members, do: IRC.UserTrack.joined(state.id, %{network: "matrix", nick: m, user: m, host: "matrix."}, [], true) + + accounts = IRC.UserTrack.channel(state.network, state.channel) + |> Enum.map(fn(tuple) -> IRC.UserTrack.User.from_tuple(tuple).account end) + |> Enum.uniq() + |> Enum.each(fn(account_id) -> + introduce_irc_account(account_id, state) + end) + + {:noreply, state} + end + + def handle_info({:irc, :text, message}, state), do: handle_irc(message, state) + def handle_info({:irc, :out, message}, state), do: handle_irc(message, state) + def handle_info({:irc, :trigger, _, message}, state), do: handle_irc(message, state) + def handle_info({:irc, :event, event}, state), do: handle_irc(event, state) + def handle_info({:matrix_event, event}, state) do + if myself?(event.user_id) do + {:noreply, state} + else + handle_matrix(event, state) + end + end + + def handle_irc(message = %IRC.Message{account: account}, state) do + unless Map.get(message.meta, :puppet) && Map.get(message.meta, :from) == self() do + opts = if Map.get(message.meta, :self) || is_nil(account) do + [] + else + mxid = Matrix.get_or_create_matrix_user(account.id) + [user_id: mxid] + end + Client.Room.send_message(client(opts),state.id, message.text) + end + {:noreply, state} + end + + def handle_irc(%{type: :join, account_id: account_id}, state) do + introduce_irc_account(account_id, state) + {:noreply, state} + end + + def handle_irc(event, state) do + Logger.warn("Skipped irc event #{inspect event}") + {:noreply, state} + end + + def handle_matrix(event = %{type: "m.room.member", user_id: user_id, content: %{"membership" => "join"}}, state) do + _account = get_account(event, state) + IRC.UserTrack.joined(state.id, %{network: "matrix", nick: user_id, user: user_id, host: "matrix."}, [], true) + {:noreply, state} + end + + def handle_matrix(event = %{type: "m.room.member", user_id: user_id, content: %{"membership" => "leave"}}, state) do + IRC.UserTrack.parted(state.id, %{network: "matrix", nick: user_id}) + {:noreply, state} + end + + def handle_matrix(event = %{type: "m.room.message", user_id: user_id, content: %{"msgtype" => "m.text", "body" => text}}, state) do + IRC.send_message_as(get_account(event, state), state.network, state.channel, text, true) + {:noreply, state} + end + + def handle_matrix(event, state) do + Logger.warn("Skipped matrix event #{inspect event}") + {:noreply, state} + end + + def get_account(%{user_id: user_id}, %{id: id}) do + IRC.Account.find_by_nick("matrix", user_id) + end + + defp introduce_irc_account(account_id, state) do + mxid = Matrix.get_or_create_matrix_user(account_id) + account = IRC.Account.get(account_id) + user = IRC.UserTrack.find_by_account(state.network, account) + base_nick = if(user, do: user.nick, else: account.name) + case Client.Profile.put_displayname(client(user_id: mxid), base_nick) do + :ok -> :ok + error -> + Logger.warn("Failed to update profile for #{mxid}: #{inspect error}") + end + case Client.Room.join(client(user_id: mxid), state.id) do + {:ok, _} -> :ok + error -> + Logger.warn("Failed to join room for #{mxid}: #{inspect error}") + end + :ok + end + +end |