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: 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),
%IRC.Connection{} <- 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