summaryrefslogtreecommitdiff
path: root/lib/plugins/helpers/temp_ref.ex
blob: f4407d8a10e2b271ec99f65974faae92a07688a5 (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
defmodule Nola.Plugins.TempRefHelper do
  @moduledoc """
  This module allows to easily implement local temporary simple references for easy access from IRC.

  For example, your plugin output could be acted on, and instead of giving the burden for the user to
  write or copy that uuid, you could give them a small alphanumeric reference to use instead.

  You can configure how many and for how long the references are kept.

  ## Usage

  `import Irc.Plugin.TempRef`

  ```elixir
  defmodule Irc.MyPlugin do
    defstruct [:temprefs]

    def init(_) do
      #
      {:ok, %__MODULE__{temprefs: new_temp_refs()}
    end
  end
  ```
  """

  defstruct [:refs, :max, :expire, :build_fun, :build_increase_fun, :build_options]

  defmodule SimpleAlphaNumericBuilder do
    def build(options) do
      length = Keyword.get(options, :length, 3)
      for _ <- 1..length, into: "", do: <<Enum.random('bcdfghjkmpqtrvwxy2346789')>>
    end

    def increase(options) do
      Keyword.put(options, :length, Keyword.get(options, :length, 3) + 1)
    end
  end

  def new_temp_refs(options \\ []) do
    %__MODULE__{
      refs: Keyword.get(options, :init_refs, []),
      max: Keyword.get(options, :max, []),
      expire: Keyword.get(options, :expire, :infinity),
      build_fun: Keyword.get(options, :build_fun, &__MODULE__.SimpleAlphaNumericBuilder.build/1),
      build_increase_fun:
        Keyword.get(
          options,
          :build_increase_fun,
          &__MODULE__.SimpleAlphaNumericBuilder.increase/1
        ),
      build_options: Keyword.get(options, :build_options, length: 3)
    }
  end

  def janitor_refs(state = %__MODULE__{}) do
    if length(state.refs) > state.max do
      %__MODULE__{refs: state.refs |> Enum.reverse() |> tl() |> Enum.reverse()}
    else
      state
    end
  end

  def put_temp_ref(data, state = %__MODULE__{}) do
    state = janitor_refs(state)
    key = new_nonexisting_key(state)

    if key do
      ref = {key, DateTime.utc_now(), data}
      {key, %__MODULE__{state | refs: [ref | state.refs]}}
    else
      {nil, state}
    end
  end

  def lookup_temp_ref(key, state, default \\ nil) do
    case List.keyfind(state.refs, key, 0) do
      {_, _, data} -> data
      _ -> default
    end
  end

  defp new_nonexisting_key(state, i) when i > 50 do
    nil
  end

  defp new_nonexisting_key(state = %__MODULE__{refs: refs}, i \\ 1) do
    build_options =
      if rem(i, 5) == 0 do
        state.build_increase_fun.(state.build_options)
      else
        state.build_options
      end

    key = state.build_fun.(state.build_options)

    if !List.keymember?(refs, key, 0) do
      key
    else
      new_nonexisting_key(state, i + 1)
    end
  end
end