29a5fa99cacf8a1045c9ee34266668c96dae1c74
[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 defp fetch_and_decode(from) do
241 with {:ok, json} <- fetch(from) do
242 Jason.decode!(json)
243 end
244 end
245
246 defp fetch("http" <> _ = from) do
247 with {:ok, %{body: body}} <- Tesla.get(client(), from) do
248 {:ok, body}
249 end
250 end
251
252 defp fetch(path), do: File.read(path)
253
254 defp parse_global_opts(args) do
255 OptionParser.parse(
256 args,
257 strict: [
258 manifest: :string
259 ],
260 aliases: [
261 m: :manifest
262 ]
263 )
264 end
265
266 defp client do
267 middleware = [
268 {Tesla.Middleware.FollowRedirects, [max_redirects: 3]}
269 ]
270
271 Tesla.client(middleware)
272 end
273
274 defp default_manifest, do: Pleroma.Config.get!([:emoji, :default_manifest])
275 end