Merge branch 'restart-fix-for-mix-tasks' into 'develop'
[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
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, _} = :zip.unzip(binary_archive, cwd: String.to_charlist(tmp_pack_dir))
190
191 emoji_map = Pleroma.Emoji.Loader.make_shortcode_to_file_map(tmp_pack_dir, exts)
192
193 File.write!(files_name, Jason.encode!(emoji_map, pretty: true))
194
195 IO.puts("""
196
197 #{files_name} has been created and contains the list of all found emojis in the pack.
198 Please review the files in the remove those not needed.
199 """)
200
201 if File.exists?("index.json") do
202 existing_data = File.read!("index.json") |> Jason.decode!()
203
204 File.write!(
205 "index.json",
206 Jason.encode!(
207 Map.merge(
208 existing_data,
209 pack_json
210 ),
211 pretty: true
212 )
213 )
214
215 IO.puts("index.json file has been update with the #{name} pack")
216 else
217 File.write!("index.json", Jason.encode!(pack_json, pretty: true))
218
219 IO.puts("index.json has been created with the #{name} pack")
220 end
221 end
222
223 defp fetch_manifest(from) do
224 Jason.decode!(
225 if String.starts_with?(from, "http") do
226 Tesla.get!(client(), from).body
227 else
228 File.read!(from)
229 end
230 )
231 end
232
233 defp parse_global_opts(args) do
234 OptionParser.parse(
235 args,
236 strict: [
237 manifest: :string
238 ],
239 aliases: [
240 m: :manifest
241 ]
242 )
243 end
244
245 defp client do
246 middleware = [
247 {Tesla.Middleware.FollowRedirects, [max_redirects: 3]}
248 ]
249
250 Tesla.client(middleware)
251 end
252
253 defp default_manifest, do: Pleroma.Config.get!([:emoji, :default_manifest])
254 end