Merge branch 'require-signature' into 'develop'
[akkoma] / lib / mix / tasks / 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 Mix.Tasks.Pleroma.Emoji do
6 use Mix.Task
7
8 @shortdoc "Manages emoji packs"
9 @moduledoc File.read!("docs/administration/CLI_tasks/emoji.md")
10
11 def run(["ls-packs" | args]) do
12 Mix.Pleroma.start_pleroma()
13 Application.ensure_all_started(:hackney)
14
15 {options, [], []} = parse_global_opts(args)
16
17 manifest =
18 fetch_manifest(if options[:manifest], do: options[:manifest], else: default_manifest())
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 Mix.Pleroma.start_pleroma()
40 Application.ensure_all_started(:hackney)
41
42 {options, pack_names, []} = parse_global_opts(args)
43
44 manifest_url = if options[:manifest], do: options[:manifest], else: default_manifest()
45
46 manifest = fetch_manifest(manifest_url)
47
48 for pack_name <- pack_names do
49 if Map.has_key?(manifest, pack_name) do
50 pack = manifest[pack_name]
51 src_url = pack["src"]
52
53 IO.puts(
54 IO.ANSI.format([
55 "Downloading ",
56 :bright,
57 pack_name,
58 :normal,
59 " from ",
60 :underline,
61 src_url
62 ])
63 )
64
65 binary_archive = Tesla.get!(client(), src_url).body
66 archive_sha = :crypto.hash(:sha256, binary_archive) |> Base.encode16()
67
68 sha_status_text = ["SHA256 of ", :bright, pack_name, :normal, " source file is ", :bright]
69
70 if archive_sha == String.upcase(pack["src_sha256"]) do
71 IO.puts(IO.ANSI.format(sha_status_text ++ [:green, "OK"]))
72 else
73 IO.puts(IO.ANSI.format(sha_status_text ++ [:red, "BAD"]))
74
75 raise "Bad SHA256 for #{pack_name}"
76 end
77
78 # The url specified in files should be in the same directory
79 files_url = Path.join(Path.dirname(manifest_url), pack["files"])
80
81 IO.puts(
82 IO.ANSI.format([
83 "Fetching the file list for ",
84 :bright,
85 pack_name,
86 :normal,
87 " from ",
88 :underline,
89 files_url
90 ])
91 )
92
93 files = Tesla.get!(client(), files_url).body |> Jason.decode!()
94
95 IO.puts(IO.ANSI.format(["Unpacking ", :bright, pack_name]))
96
97 pack_path =
98 Path.join([
99 Pleroma.Config.get!([:instance, :static_dir]),
100 "emoji",
101 pack_name
102 ])
103
104 files_to_unzip =
105 Enum.map(
106 files,
107 fn {_, f} -> to_charlist(f) end
108 )
109
110 {:ok, _} =
111 :zip.unzip(binary_archive,
112 cwd: pack_path,
113 file_list: files_to_unzip
114 )
115
116 IO.puts(IO.ANSI.format(["Writing pack.json for ", :bright, pack_name]))
117
118 pack_json = %{
119 pack: %{
120 "license" => pack["license"],
121 "homepage" => pack["homepage"],
122 "description" => pack["description"],
123 "fallback-src" => pack["src"],
124 "fallback-src-sha256" => pack["src_sha256"],
125 "share-files" => true
126 },
127 files: files
128 }
129
130 File.write!(Path.join(pack_path, "pack.json"), Jason.encode!(pack_json, pretty: true))
131 else
132 IO.puts(IO.ANSI.format([:bright, :red, "No pack named \"#{pack_name}\" found"]))
133 end
134 end
135 end
136
137 def run(["gen-pack", src]) do
138 Application.ensure_all_started(:hackney)
139
140 proposed_name = Path.basename(src) |> Path.rootname()
141 name = String.trim(IO.gets("Pack name [#{proposed_name}]: "))
142 # If there's no name, use the default one
143 name = if String.length(name) > 0, do: name, else: proposed_name
144
145 license = String.trim(IO.gets("License: "))
146 homepage = String.trim(IO.gets("Homepage: "))
147 description = String.trim(IO.gets("Description: "))
148
149 proposed_files_name = "#{name}.json"
150 files_name = String.trim(IO.gets("Save file list to [#{proposed_files_name}]: "))
151 files_name = if String.length(files_name) > 0, do: files_name, else: proposed_files_name
152
153 default_exts = [".png", ".gif"]
154 default_exts_str = Enum.join(default_exts, " ")
155
156 exts =
157 String.trim(
158 IO.gets("Emoji file extensions (separated with spaces) [#{default_exts_str}]: ")
159 )
160
161 exts =
162 if String.length(exts) > 0 do
163 String.split(exts, " ")
164 |> Enum.filter(fn e -> e |> String.trim() |> String.length() > 0 end)
165 else
166 default_exts
167 end
168
169 IO.puts("Downloading the pack and generating SHA256")
170
171 binary_archive = Tesla.get!(client(), src).body
172 archive_sha = :crypto.hash(:sha256, binary_archive) |> Base.encode16()
173
174 IO.puts("SHA256 is #{archive_sha}")
175
176 pack_json = %{
177 name => %{
178 license: license,
179 homepage: homepage,
180 description: description,
181 src: src,
182 src_sha256: archive_sha,
183 files: files_name
184 }
185 }
186
187 tmp_pack_dir = Path.join(System.tmp_dir!(), "emoji-pack-#{name}")
188
189 {:ok, _} =
190 :zip.unzip(
191 binary_archive,
192 cwd: tmp_pack_dir
193 )
194
195 emoji_map = Pleroma.Emoji.Loader.make_shortcode_to_file_map(tmp_pack_dir, exts)
196
197 File.write!(files_name, Jason.encode!(emoji_map, pretty: true))
198
199 IO.puts("""
200
201 #{files_name} has been created and contains the list of all found emojis in the pack.
202 Please review the files in the remove those not needed.
203 """)
204
205 if File.exists?("index.json") do
206 existing_data = File.read!("index.json") |> Jason.decode!()
207
208 File.write!(
209 "index.json",
210 Jason.encode!(
211 Map.merge(
212 existing_data,
213 pack_json
214 ),
215 pretty: true
216 )
217 )
218
219 IO.puts("index.json file has been update with the #{name} pack")
220 else
221 File.write!("index.json", Jason.encode!(pack_json, pretty: true))
222
223 IO.puts("index.json has been created with the #{name} pack")
224 end
225 end
226
227 defp fetch_manifest(from) do
228 Jason.decode!(
229 if String.starts_with?(from, "http") do
230 Tesla.get!(client(), from).body
231 else
232 File.read!(from)
233 end
234 )
235 end
236
237 defp parse_global_opts(args) do
238 OptionParser.parse(
239 args,
240 strict: [
241 manifest: :string
242 ],
243 aliases: [
244 m: :manifest
245 ]
246 )
247 end
248
249 defp client do
250 middleware = [
251 {Tesla.Middleware.FollowRedirects, [max_redirects: 3]}
252 ]
253
254 Tesla.client(middleware)
255 end
256
257 defp default_manifest, do: Pleroma.Config.get!([:emoji, :default_manifest])
258 end