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