8f52ee98d2245aa6db36206c735fabff6034e331
[akkoma] / lib / mix / tasks / pleroma / emoji.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Mix.Tasks.Pleroma.Emoji do
6 use Mix.Task
7 import Mix.Pleroma
8
9 @shortdoc "Manages emoji packs"
10 @moduledoc File.read!("docs/administration/CLI_tasks/emoji.md")
11
12 def run(["ls-packs" | args]) do
13 start_pleroma()
14
15 {options, [], []} = parse_global_opts(args)
16
17 url_or_path = options[:manifest] || default_manifest()
18 manifest = fetch_and_decode!(url_or_path)
19
20 Enum.each(manifest, fn {name, info} ->
21 to_print = [
22 {"Name", name},
23 {"Homepage", info["homepage"]},
24 {"Description", info["description"]},
25 {"License", info["license"]},
26 {"Source", info["src"]}
27 ]
28
29 for {param, value} <- to_print do
30 IO.puts(IO.ANSI.format([:bright, param, :normal, ": ", value]))
31 end
32
33 # A newline
34 IO.puts("")
35 end)
36 end
37
38 def run(["get-packs" | args]) do
39 start_pleroma()
40
41 {options, pack_names, []} = parse_global_opts(args)
42
43 url_or_path = options[:manifest] || default_manifest()
44
45 manifest = fetch_and_decode!(url_or_path)
46
47 for pack_name <- pack_names do
48 if Map.has_key?(manifest, pack_name) do
49 pack = manifest[pack_name]
50 src = pack["src"]
51
52 IO.puts(
53 IO.ANSI.format([
54 "Downloading ",
55 :bright,
56 pack_name,
57 :normal,
58 " from ",
59 :underline,
60 src
61 ])
62 )
63
64 {:ok, binary_archive} = fetch(src)
65 archive_sha = :crypto.hash(:sha256, binary_archive) |> Base.encode16()
66
67 sha_status_text = ["SHA256 of ", :bright, pack_name, :normal, " source file is ", :bright]
68
69 if archive_sha == String.upcase(pack["src_sha256"]) do
70 IO.puts(IO.ANSI.format(sha_status_text ++ [:green, "OK"]))
71 else
72 IO.puts(IO.ANSI.format(sha_status_text ++ [:red, "BAD"]))
73
74 raise "Bad SHA256 for #{pack_name}"
75 end
76
77 # The location specified in files should be in the same directory
78 files_loc =
79 url_or_path
80 |> Path.dirname()
81 |> Path.join(pack["files"])
82
83 IO.puts(
84 IO.ANSI.format([
85 "Fetching the file list for ",
86 :bright,
87 pack_name,
88 :normal,
89 " from ",
90 :underline,
91 files_loc
92 ])
93 )
94
95 files = fetch_and_decode!(files_loc)
96
97 IO.puts(IO.ANSI.format(["Unpacking ", :bright, pack_name]))
98
99 pack_path =
100 Path.join([
101 Pleroma.Config.get!([:instance, :static_dir]),
102 "emoji",
103 pack_name
104 ])
105
106 files_to_unzip =
107 Enum.map(
108 files,
109 fn {_, f} -> to_charlist(f) end
110 )
111
112 {:ok, _} =
113 :zip.unzip(binary_archive,
114 cwd: pack_path,
115 file_list: files_to_unzip
116 )
117
118 IO.puts(IO.ANSI.format(["Writing pack.json for ", :bright, pack_name]))
119
120 pack_json = %{
121 pack: %{
122 "license" => pack["license"],
123 "homepage" => pack["homepage"],
124 "description" => pack["description"],
125 "fallback-src" => pack["src"],
126 "fallback-src-sha256" => pack["src_sha256"],
127 "share-files" => true
128 },
129 files: files
130 }
131
132 File.write!(Path.join(pack_path, "pack.json"), Jason.encode!(pack_json, pretty: true))
133 else
134 IO.puts(IO.ANSI.format([:bright, :red, "No pack named \"#{pack_name}\" found"]))
135 end
136 end
137 end
138
139 def run(["gen-pack" | args]) do
140 start_pleroma()
141
142 {opts, [src], []} =
143 OptionParser.parse(
144 args,
145 strict: [
146 name: :string,
147 license: :string,
148 homepage: :string,
149 description: :string,
150 files: :string,
151 extensions: :string
152 ]
153 )
154
155 proposed_name = Path.basename(src) |> Path.rootname()
156 name = get_option(opts, :name, "Pack name:", proposed_name)
157 license = get_option(opts, :license, "License:")
158 homepage = get_option(opts, :homepage, "Homepage:")
159 description = get_option(opts, :description, "Description:")
160
161 proposed_files_name = "#{name}_files.json"
162 files_name = get_option(opts, :files, "Save file list to:", proposed_files_name)
163
164 default_exts = [".png", ".gif"]
165
166 custom_exts =
167 get_option(
168 opts,
169 :extensions,
170 "Emoji file extensions (separated with spaces):",
171 Enum.join(default_exts, " ")
172 )
173 |> String.split(" ", trim: true)
174
175 exts =
176 if MapSet.equal?(MapSet.new(default_exts), MapSet.new(custom_exts)) do
177 default_exts
178 else
179 custom_exts
180 end
181
182 IO.puts("Using #{Enum.join(exts, " ")} extensions")
183
184 IO.puts("Downloading the pack and generating SHA256")
185
186 binary_archive = Tesla.get!(client(), src).body
187 archive_sha = :crypto.hash(:sha256, binary_archive) |> Base.encode16()
188
189 IO.puts("SHA256 is #{archive_sha}")
190
191 pack_json = %{
192 name => %{
193 license: license,
194 homepage: homepage,
195 description: description,
196 src: src,
197 src_sha256: archive_sha,
198 files: files_name
199 }
200 }
201
202 tmp_pack_dir = Path.join(System.tmp_dir!(), "emoji-pack-#{name}")
203
204 {:ok, _} = :zip.unzip(binary_archive, cwd: String.to_charlist(tmp_pack_dir))
205
206 emoji_map = Pleroma.Emoji.Loader.make_shortcode_to_file_map(tmp_pack_dir, exts)
207
208 File.write!(files_name, Jason.encode!(emoji_map, pretty: true))
209
210 IO.puts("""
211
212 #{files_name} has been created and contains the list of all found emojis in the pack.
213 Please review the files in the pack and remove those not needed.
214 """)
215
216 pack_file = "#{name}.json"
217
218 if File.exists?(pack_file) do
219 existing_data = File.read!(pack_file) |> Jason.decode!()
220
221 File.write!(
222 pack_file,
223 Jason.encode!(
224 Map.merge(
225 existing_data,
226 pack_json
227 ),
228 pretty: true
229 )
230 )
231
232 IO.puts("#{pack_file} has been updated with the #{name} pack")
233 else
234 File.write!(pack_file, Jason.encode!(pack_json, pretty: true))
235
236 IO.puts("#{pack_file} has been created with the #{name} pack")
237 end
238 end
239
240 def run(["reload"]) do
241 start_pleroma()
242 Pleroma.Emoji.reload()
243 IO.puts("Emoji packs have been reloaded.")
244 end
245
246 defp fetch_and_decode!(from) do
247 with {:ok, json} <- fetch(from) do
248 Jason.decode!(json)
249 else
250 {:error, error} -> raise "#{from} cannot be fetched. Error: #{error} occur."
251 end
252 end
253
254 defp fetch("http" <> _ = from) do
255 with {:ok, %{body: body}} <- Tesla.get(client(), from) do
256 {:ok, body}
257 end
258 end
259
260 defp fetch(path), do: File.read(path)
261
262 defp parse_global_opts(args) do
263 OptionParser.parse(
264 args,
265 strict: [
266 manifest: :string
267 ],
268 aliases: [
269 m: :manifest
270 ]
271 )
272 end
273
274 defp client do
275 middleware = [
276 {Tesla.Middleware.FollowRedirects, [max_redirects: 3]}
277 ]
278
279 Tesla.client(middleware)
280 end
281
282 defp default_manifest, do: Pleroma.Config.get!([:emoji, :default_manifest])
283 end