defmodule Nola.Matrix do require Logger alias Polyjuice.Client @behaviour MatrixAppService.Adapter.Room @behaviour MatrixAppService.Adapter.Transaction @behaviour MatrixAppService.Adapter.User @env Mix.env def dets(part) do (Nola.data_path() <> "/matrix-#{to_string(part)}.dets") |> String.to_charlist() end def setup() do {:ok, _} = :dets.open_file(dets(:rooms), []) {:ok, _} = :dets.open_file(dets(:room_aliases), []) {:ok, _} = :dets.open_file(dets(:users), []) :ok end def myself?("@_dev:random.sh"), do: true def myself?("@_bot:random.sh"), do: true def myself?("@_dev."<>_), do: true def myself?("@_bot."<>_), do: true def myself?(_), do: false def mxc_to_http(mxc = "mxc://"<>_) do uri = URI.parse(mxc) %URI{uri | scheme: "https", path: "/_matrix/media/r0/download/#{uri.authority}#{uri.path}"} |> URI.to_string() end def get_or_create_matrix_user(id) do if mxid = lookup_user(id) do mxid else opts = [ type: "m.login.application_service", inhibit_login: true, device_id: "APP_SERVICE", initial_device_display_name: "Application Service", username: if(@env == :dev, do: "_dev.#{id}", else: "_bot.#{id}") ] Logger.debug("Registering user for #{id}") {:ok, %{"user_id" => mxid}} = Polyjuice.Client.LowLevel.register(client(), opts) :dets.insert(dets(:users), {id, mxid}) end end def lookup_user(id) do case :dets.lookup(dets(:users), id) do [{_, matrix_id}] -> matrix_id _ -> nil end end def user_name("@"<>name) do [username, _] = String.split(name, ":", parts: 2) username end def application_childs() do import Supervisor.Spec [ supervisor(Nola.Matrix.Room.Supervisor, [], [name: Nola.Irc.PuppetConnection.Supervisor]), ] end def after_start() do rooms = :dets.foldl(fn({id, _, _, _}, acc) -> [id | acc] end, [], dets(:rooms)) for room <- rooms, do: Nola.Matrix.Room.start(room) end def lookup_room(room) do case :dets.lookup(dets(:rooms), room) do [{_, network, channel, opts}] -> {:ok, Map.merge(opts, %{network: network, channel: channel})} _ -> {:error, :no_such_room} end end def lookup_room_alias(room_alias) do case :dets.lookup(dets(:room_aliases), room_alias) do [{_, room_id}] -> {:ok, room_id} _ -> {:error, :no_such_room_alias} end end def lookup_or_create_room(room_alias) do case lookup_room_alias(room_alias) do {:ok, room_id} -> {:ok, room_id} {:error, :no_such_room_alias} -> create_room(room_alias) end end def create_room(room_alias) do Logger.debug("Matrix: creating room #{inspect room_alias}") localpart = localpart(room_alias) with {:ok, network, channel} <- extract_network_channel_from_localpart(localpart), %Nola.Irc.Connection{} <- Nola.Irc.Connection.get_network(network, channel), room = [visibility: :public, room_alias_name: localpart, name: if(network == "random", do: channel, else: "#{network}/#{channel}")], {:ok, %{"room_id" => room_id}} <- Client.Room.create_room(client(), room) do Logger.info("Matrix: created room #{room_alias} #{room_id}") :dets.insert(dets(:rooms), {room_id, network, channel, %{}}) :dets.insert(dets(:room_aliases), {room_alias, room_id}) {:ok, room_id} else nil -> {:error, :no_such_network_channel} error -> error end end def localpart(room_alias) do [<<"#", localpart :: binary>>, _] = String.split(room_alias, ":", parts: 2) localpart end def extract_network_channel_from_localpart(localpart) do s = localpart |> String.replace("dev.", "") |> String.split("/", parts: 2) case s do [network, channel] -> {:ok, network, channel} [channel] -> {:ok, "random", channel} _ -> {:error, :invalid_localpart} end end @impl MatrixAppService.Adapter.Room def query_alias(room_alias) do case lookup_or_create_room(room_alias) do {:ok, room_id} -> Nola.Matrix.Room.start(room_id) :ok error -> error end end @impl MatrixAppService.Adapter.Transaction def new_event(event = %MatrixAppService.Event{}) do Logger.debug("New matrix event: #{inspect event}") if event.room_id do Nola.Matrix.Room.start_and_send_matrix_event(event.room_id, event) end :noop end @impl MatrixAppService.Adapter.User def query_user(user_id) do Logger.warn("Matrix lookup user: #{inspect user_id}") :error end def client(opts \\ []) do base_url = Application.get_env(:matrix_app_service, :base_url) access_token = Application.get_env(:matrix_app_service, :access_token) default_opts = [ access_token: access_token, device_id: "APP_SERVICE", application_service: true, user_id: nil ] opts = Keyword.merge(default_opts, opts) Polyjuice.Client.LowLevel.create(base_url, opts) end end