Runtime configured emojis
[akkoma] / lib / pleroma / emoji.ex
1 defmodule Pleroma.Emoji do
2 @moduledoc """
3 The emojis are loaded from:
4
5 * the built-in Finmojis (if enabled in configuration),
6 * the files: `config/emoji.txt` and `config/custom_emoji.txt`
7 * glob paths
8
9 This GenServer stores in an ETS table the list of the loaded emojis, and also allows to reload the list at runtime.
10 """
11 use GenServer
12 @ets __MODULE__.Ets
13 @ets_options [:set, :protected, :named_table, {:read_concurrency, true}]
14
15 @doc false
16 def start_link() do
17 GenServer.start_link(__MODULE__, [], name: __MODULE__)
18 end
19
20 @doc "Reloads the emojis from disk."
21 @spec reload() :: :ok
22 def reload() do
23 GenServer.call(__MODULE__, :reload)
24 end
25
26 @doc "Returns the path of the emoji `name`."
27 @spec get(String.t()) :: String.t() | nil
28 def get(name) do
29 case :ets.lookup(@ets, name) do
30 [{_, path}] -> path
31 _ -> nil
32 end
33 end
34
35 @doc "Returns all the emojos!!"
36 @spec get_all() :: [{String.t(), String.t()}, ...]
37 def get_all() do
38 :ets.tab2list(@ets)
39 end
40
41 @doc false
42 def init(_) do
43 @ets = :ets.new(@ets, @ets_options)
44 {:ok, nil, {:continue, :reload}}
45 end
46
47 @doc false
48 def handle_continue(:reload, state) do
49 load()
50 {:noreply, state}
51 end
52
53 @doc false
54 def handle_call(:reload, _from, state) do
55 load()
56 {:reply, :ok, state}
57 end
58
59 @doc false
60 def terminate(_, _) do
61 :ok
62 end
63
64 @doc false
65 def code_change(_old_vsn, state, _extra) do
66 load()
67 {:ok, state}
68 end
69
70 defp load() do
71 emojis =
72 (load_finmoji(Keyword.get(Application.get_env(:pleroma, :instance), :finmoji_enabled)) ++
73 load_from_file("config/emoji.txt") ++
74 load_from_file("config/custom_emoji.txt") ++
75 load_from_globs(
76 Keyword.get(Application.get_env(:pleroma, :emoji, []), :shortcode_globs, [])
77 ))
78 |> Enum.reject(fn value -> value == nil end)
79
80 true = :ets.insert(@ets, emojis)
81 :ok
82 end
83
84 @finmoji [
85 "a_trusted_friend",
86 "alandislands",
87 "association",
88 "auroraborealis",
89 "baby_in_a_box",
90 "bear",
91 "black_gold",
92 "christmasparty",
93 "crosscountryskiing",
94 "cupofcoffee",
95 "education",
96 "fashionista_finns",
97 "finnishlove",
98 "flag",
99 "forest",
100 "four_seasons_of_bbq",
101 "girlpower",
102 "handshake",
103 "happiness",
104 "headbanger",
105 "icebreaker",
106 "iceman",
107 "joulutorttu",
108 "kaamos",
109 "kalsarikannit_f",
110 "kalsarikannit_m",
111 "karjalanpiirakka",
112 "kicksled",
113 "kokko",
114 "lavatanssit",
115 "losthopes_f",
116 "losthopes_m",
117 "mattinykanen",
118 "meanwhileinfinland",
119 "moominmamma",
120 "nordicfamily",
121 "out_of_office",
122 "peacemaker",
123 "perkele",
124 "pesapallo",
125 "polarbear",
126 "pusa_hispida_saimensis",
127 "reindeer",
128 "sami",
129 "sauna_f",
130 "sauna_m",
131 "sauna_whisk",
132 "sisu",
133 "stuck",
134 "suomimainittu",
135 "superfood",
136 "swan",
137 "the_cap",
138 "the_conductor",
139 "the_king",
140 "the_voice",
141 "theoriginalsanta",
142 "tomoffinland",
143 "torillatavataan",
144 "unbreakable",
145 "waiting",
146 "white_nights",
147 "woollysocks"
148 ]
149 defp load_finmoji(true) do
150 Enum.map(@finmoji, fn finmoji ->
151 {finmoji, "/finmoji/128px/#{finmoji}-128.png"}
152 end)
153 end
154
155 defp load_finmoji(_), do: :ok
156
157 defp load_from_file(file) do
158 if File.exists?(file) do
159 load_from_file_stream(File.stream!(file))
160 else
161 []
162 end
163 end
164
165 defp load_from_file_stream(stream) do
166 stream
167 |> Stream.map(&String.strip/1)
168 |> Stream.map(fn line ->
169 case String.split(line, ~r/,\s*/) do
170 [name, file] -> {name, file}
171 _ -> nil
172 end
173 end)
174 |> Enum.to_list()
175 end
176
177 defp load_from_globs(globs) do
178 static_path = Path.join(:code.priv_dir(:pleroma), "static")
179
180 paths =
181 Enum.map(globs, fn glob ->
182 Path.join(static_path, glob)
183 |> Path.wildcard()
184 end)
185 |> Enum.concat()
186
187 Enum.map(paths, fn path ->
188 shortcode = Path.basename(path, Path.extname(path))
189 external_path = Path.join("/", Path.relative_to(path, static_path))
190 {shortcode, external_path}
191 end)
192 end
193 end