summaryrefslogblamecommitdiff
path: root/lib/lsg_matrix/room.ex
blob: 72b02c4c4772d490247514c5136d48902fd3d74a (plain) (tree)










































                                                                                                  
                      

         
                                                  














                                      


                                             
 



                                                                                                                
 







                                                                                               







                                                        


                                                             

           


           

                        


                                                                                                                           
                        






































                                                                                      






                                                                                                             
















                                                                                                                                     

                                                                                            





























                                                                             
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
        :ignore -> nil
      end
    end
    if(pid, do: 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
    case Matrix.lookup_room(room_id) do
      {:ok, state} ->
        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}}
      error ->
        Logger.info("Received event for nonexistent room #{inspect room_id}: #{inspect error}")
        :ignore
    end
  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]
        else
          # XXX: The user left, remove from IRC.Memberships ?
          acc
        end
      else
        acc
      end
    end)
    |> Enum.filter(& &1)

    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.filter(& &1)
    |> 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(%{type: quit_or_part, account_id: account_id}, state) when quit_or_part in [:quit, :part] do
    mxid = Matrix.get_or_create_matrix_user(account_id)
    Client.Room.leave(client(user_id: mxid), state.id)
    {: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