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