summaryrefslogtreecommitdiff
path: root/lib/matrix.ex
blob: 0ad08361c0f2d096e30549b9b6d9f8f4f48af7b9 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
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