summaryrefslogtreecommitdiff
path: root/lib/polyjuice/client/filter.ex
blob: fb8c6f154ab08a24c6e282fce0f8f87b8dbe7a3f (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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
# Copyright 2019 Hubert Chathi <hubert@uhoreg.ca>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

defmodule Polyjuice.Client.Filter do
  @moduledoc ~S"""
  Build filters.

  https://matrix.org/docs/spec/client_server/r0.5.0#filtering

  The functions in this module can be chained to create more complex filters.

  Examples:

      iex> Polyjuice.Client.Filter.include_state_types(["m.room.member"])
      ...> |> Polyjuice.Client.Filter.limit_timeline_events(10)
      ...> |> Polyjuice.Client.Filter.lazy_loading()
      %{
        "room" => %{
          "state" => %{
            "types" => ["m.room.member"],
            "lazy_load_members" => true
          },
          "timeline" => %{
            "lazy_load_members" => true,
            "limit" => 10
          }
        }
      }

  """

  @doc """
  Update the value in a filter.

  The `key_path` is a list of keys to traverse to find the element to update.
  The `initial` and `func` arguments are like the corresponding arguments to
  `Map.update`.

  Examples:

      iex> Polyjuice.Client.Filter.update(
      ...>   %{
      ...>     "presence" => %{
      ...>       "not_senders" => ["@alice:example.com"]
      ...>     }
      ...>   },
      ...>   ["presence", "not_senders"],
      ...>   ["@bob:example.com"],
      ...>   &Enum.concat(&1, ["@bob:example.com"])
      ...>  )
      %{
        "presence" => %{
          "not_senders" => ["@alice:example.com", "@bob:example.com"]
        }
      }

      iex> Polyjuice.Client.Filter.update(
      ...>   %{
      ...>     "presence" => %{
      ...>       "not_senders" => ["@alice:example.com"]
      ...>     }
      ...>   },
      ...>   ["presence", "senders"],
      ...>   ["@bob:example.com"],
      ...>   &Enum.concat(&1, ["@bob:example.com"])
      ...>  )
      %{
        "presence" => %{
          "not_senders" => ["@alice:example.com"],
          "senders" => ["@bob:example.com"]
        }
      }

  """
  @spec update(map, [String.t()], Any, (Any -> Any)) :: map
  def update(filter, key_path, initial, func)

  def update(filter, [key], initial, func) when is_map(filter) do
    Map.update(filter, key, initial, func)
  end

  def update(filter, [key | rest], initial, func) when is_map(filter) do
    Map.put(filter, key, update(Map.get(filter, key, %{}), rest, initial, func))
  end

  @doc """
  Set the value in a filter.

  The `key_path` is a list of keys to traverse to find the element to update.
  The `initial` and `func` arguments are like the corresponding arguments to
  `Map.update`.

      iex> Polyjuice.Client.Filter.put(
      ...>   %{
      ...>     "presence" => %{
      ...>       "not_senders" => ["@alice:example.com"]
      ...>     }
      ...>   },
      ...>   ["presence", "not_senders"],
      ...>   ["@bob:example.com"]
      ...>  )
      %{
        "presence" => %{
          "not_senders" => ["@bob:example.com"]
        }
      }

  """
  @spec put(map, [String.t()], Any) :: map
  def put(filter, key_path, val)

  def put(filter, [key], val) when is_map(filter) do
    Map.put(filter, key, val)
  end

  def put(filter, [key | rest], val) when is_map(filter) do
    Map.put(filter, key, put(Map.get(filter, key, %{}), rest, val))
  end

  defmodule Event do
    @moduledoc """
    Create `EventFilter`s, `RoomEventFilter`s, `StateFilter`s, or `RoomFilter`s.

    https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-user-userid-filter

    These are filters that form part of a `Polyjuice.Client.Filter`.  Not all of
    the functions in here may apply to all the different types of filters.
    """

    @doc """
    Add items to a list parameter in the filter.
    """
    @spec add_to_list(filter :: map, name :: String.t(), data :: list) :: map
    def add_to_list(filter \\ %{}, name, data)
        when is_map(filter) and is_binary(name) and is_list(data) do
      Polyjuice.Client.Filter.update(
        filter,
        [name],
        data,
        &Enum.concat(&1, data)
      )
    end

    @doc """
    Include certain types of events.
    """
    @spec include_types(filter :: map, types :: list) :: map
    def include_types(filter \\ %{}, types) when is_map(filter) and is_list(types),
      do: add_to_list(filter, "types", types)

    @doc """
    Exclude certain types of events.
    """
    @spec exclude_types(filter :: map, types :: list) :: map
    def exclude_types(filter \\ %{}, types) when is_map(filter) and is_list(types),
      do: add_to_list(filter, "not_types", types)

    @doc """
    Include certain senders.
    """
    @spec include_senders(filter :: map, types :: list) :: map
    def include_senders(filter \\ %{}, senders) when is_map(filter) and is_list(senders),
      do: add_to_list(filter, "senders", senders)

    @doc """
    Exclude certain senders.
    """
    @spec exclude_senders(filter :: map, senders :: list) :: map
    def exclude_senders(filter \\ %{}, senders) when is_map(filter) and is_list(senders),
      do: add_to_list(filter, "not_senders", senders)

    @doc """
    Include certain rooms.
    """
    @spec include_rooms(filter :: map, rooms :: list) :: map
    def include_rooms(filter \\ %{}, rooms) when is_map(filter) and is_list(rooms),
      do: add_to_list(filter, "rooms", rooms)

    @doc """
    Exclude certain rooms.
    """
    @spec exclude_rooms(filter :: map, rooms :: list) :: map
    def exclude_rooms(filter \\ %{}, rooms) when is_map(filter) and is_list(rooms),
      do: add_to_list(filter, "not_rooms", rooms)

    @doc """
    Set the maximum number of events to return.
    """
    @spec limit(filter :: map, limit :: integer) :: map
    def limit(filter \\ %{}, limit) when is_map(filter) and is_integer(limit),
      do: Map.put(filter, "limit", limit)

    @doc """
    Set whether to include or exclude events that have a URL.

    If `flag` is `true`, only events that have a `url` field will be included.
    If `flag` is `false`, events that have a `url` field will be excluded.
    """
    @spec contains_url(filter :: map, flag :: boolean) :: map
    def contains_url(%{} = filter \\ %{}, flag) when is_map(filter) and is_boolean(flag),
      do: Map.put(filter, "contains_url", flag)
  end

  @doc """
  Update the presence filter using a function.
  """
  @spec presence(filter :: map, f :: function) :: map
  def presence(filter, f) when is_function(f) do
    Map.put(filter, "presence", f.(Map.get(filter, "presence", %{})))
  end

  @doc """
  Allow certain types of presence events to be included.
  """
  @spec include_presence_types(filter :: map, types :: list) :: map
  def include_presence_types(filter \\ %{}, types) when is_map(filter) and is_list(types) do
    presence(filter, &Polyjuice.Client.Filter.Event.include_types(&1, types))
  end

  @doc """
  Don't allow certain types of presence events.
  """
  @spec exclude_presence_types(filter :: map, types :: list) :: map
  def exclude_presence_types(filter \\ %{}, types) do
    presence(filter, &Polyjuice.Client.Filter.Event.exclude_types(&1, types))
  end

  @doc """
  Update the ephemeral filter using a function.
  """
  @spec ephemeral(filter :: map, f :: function) :: map
  def ephemeral(filter, f) when is_function(f) do
    update(
      filter,
      ["room", "ephemeral"],
      f.(%{}),
      f
    )
  end

  @doc """
  Allow certain types of ephemeral room events to be included.
  """
  @spec include_ephemeral_types(filter :: map, types :: list) :: map
  def include_ephemeral_types(filter \\ %{}, types) do
    ephemeral(filter, &Polyjuice.Client.Filter.Event.include_types(&1, types))
  end

  @doc """
  Don't allow certain types of ephemeral room events.
  """
  @spec exclude_ephemeral_types(filter :: map, types :: list) :: map
  def exclude_ephemeral_types(filter \\ %{}, types) do
    ephemeral(filter, &Polyjuice.Client.Filter.Event.exclude_types(&1, types))
  end

  @doc """
  Update the state filter using a function.
  """
  @spec state(filter :: map, f :: function) :: map
  def state(filter, f) when is_function(f) do
    update(
      filter,
      ["room", "state"],
      f.(%{}),
      f
    )
  end

  @doc """
  Allow certain types of state events to be included.
  """
  @spec include_state_types(filter :: map, types :: list) :: map
  def include_state_types(filter \\ %{}, types) do
    state(filter, &Polyjuice.Client.Filter.Event.include_types(&1, types))
  end

  @doc """
  Don't allow certain types of state events.
  """
  @spec exclude_state_types(filter :: map, types :: list) :: map
  def exclude_state_types(filter \\ %{}, types) do
    state(filter, &Polyjuice.Client.Filter.Event.exclude_types(&1, types))
  end

  @doc """
  Update the timeline filter using a function.
  """
  @spec timeline(filter :: map, f :: function) :: map
  def timeline(filter, f) when is_function(f) do
    update(
      filter,
      ["room", "timeline"],
      f.(%{}),
      f
    )
  end

  @doc """
  Allow certain types of timeline events to be included.
  """
  @spec include_timeline_types(filter :: map, types :: list) :: map
  def include_timeline_types(filter \\ %{}, types) do
    timeline(filter, &Polyjuice.Client.Filter.Event.include_types(&1, types))
  end

  @doc """
  Don't allow certain types of timeline events.
  """
  @spec exclude_timeline_types(filter :: map, types :: list) :: map
  def exclude_timeline_types(filter \\ %{}, types) do
    timeline(filter, &Polyjuice.Client.Filter.Event.exclude_types(&1, types))
  end

  @doc """
  Set the maximum number of timeline events.
  """
  @spec limit_timeline_events(filter :: map, limit :: integer) :: map
  def limit_timeline_events(filter \\ %{}, limit) do
    timeline(filter, &Polyjuice.Client.Filter.Event.limit(&1, limit))
  end

  @spec lazy_loading(filter :: map) :: map
  def lazy_loading(filter \\ %{})

  def lazy_loading(filter) when filter == %{} do
    %{
      "room" => %{
        "state" => %{
          "lazy_load_members" => true
        },
        "timeline" => %{
          "lazy_load_members" => true
        }
      }
    }
  end

  def lazy_loading(filter) when is_map(filter) do
    filter
    |> put(["room", "state", "lazy_load_members"], true)
    |> put(["room", "timeline", "lazy_load_members"], true)
  end
end