Fix priv/static/instance/static to be just instance/static
[akkoma] / lib / mix / tasks / pleroma / emoji.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2018 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 """
10 Manages emoji packs
11
12 ## ls-packs
13
14 mix pleroma.emoji ls-packs [OPTION...]
15
16 Lists the emoji packs and metadata specified in the manifest.
17
18 ### Options
19
20 - `-m, --manifest PATH/URL` - path to a custom manifest, it can either be an URL
21 starting with `http`, in that case the manifest will be fetched from that address,
22 or a local path
23
24 ## get-packs
25
26 mix pleroma.emoji get-packs [OPTION...] PACKS
27
28 Fetches, verifies and installs the specified PACKS from the manifest into
29 the `STATIC-DIR/emoji/PACK-NAME
30
31 ### Options
32
33 - `-m, --manifest PATH/URL` - same as ls-packs
34
35 ## gen-pack
36
37 mix pleroma.emoji gen-pack PACK-URL
38
39 Creates a new manifest entry and a file list from the specified remote pack file.
40 Currently, only .zip archives are recognized as remote pack files and packs are therefore
41 assumed to be zip archives. This command is intended to run interactively and
42 will first ask you some basic questions about the pack, then download the remote
43 file and generate an MD5 signature for it, then generate an emoji file list for you.
44
45 The manifest entry will either be written to a newly created `index.json` file or appended to the existing one,
46 *replacing* the old pack with the same name if it was in the file previously.
47
48 The file list will be written to the file specified previously, *replacing* that file.
49 You _should_ check that the file list doesn't contain anything you don't need in the pack, that is,
50 anything that is not an emoji (the whole pack is downloaded, but only emoji files are extracted).
51
52 """
53
54 @default_manifest Pleroma.Config.get!([:emoji, :default_manifest])
55
56 def run(["ls-packs" | args]) do
57 Application.ensure_all_started(:hackney)
58
59 {options, [], []} = parse_global_opts(args)
60
61 manifest =
62 fetch_manifest(if options[:manifest], do: options[:manifest], else: @default_manifest)
63
64 Enum.each(manifest, fn {name, info} ->
65 to_print = [
66 {"Name", name},
67 {"Homepage", info["homepage"]},
68 {"Description", info["description"]},
69 {"License", info["license"]},
70 {"Source", info["src"]}
71 ]
72
73 for {param, value} <- to_print do
74 IO.puts(IO.ANSI.format([:bright, param, :normal, ": ", value]))
75 end
76
77 # A newline
78 IO.puts("")
79 end)
80 end
81
82 def run(["get-packs" | args]) do
83 Application.ensure_all_started(:hackney)
84
85 {options, pack_names, []} = parse_global_opts(args)
86
87 manifest_url = if options[:manifest], do: options[:manifest], else: @default_manifest
88
89 manifest = fetch_manifest(manifest_url)
90
91 for pack_name <- pack_names do
92 if Map.has_key?(manifest, pack_name) do
93 pack = manifest[pack_name]
94 src_url = pack["src"]
95
96 IO.puts(
97 IO.ANSI.format([
98 "Downloading ",
99 :bright,
100 pack_name,
101 :normal,
102 " from ",
103 :underline,
104 src_url
105 ])
106 )
107
108 binary_archive = Tesla.get!(src_url).body
109 archive_md5 = :crypto.hash(:md5, binary_archive) |> Base.encode16()
110
111 md5_status_text = ["MD5 of ", :bright, pack_name, :normal, " source file is ", :bright]
112
113 if archive_md5 == String.upcase(pack["src_md5"]) do
114 IO.puts(IO.ANSI.format(md5_status_text ++ [:green, "OK"]))
115 else
116 IO.puts(IO.ANSI.format(md5_status_text ++ [:red, "BAD"]))
117
118 raise "Bad MD5 for #{pack_name}"
119 end
120
121 # The url specified in files should be in the same directory
122 files_url = Path.join(Path.dirname(manifest_url), pack["files"])
123
124 IO.puts(
125 IO.ANSI.format([
126 "Fetching the file list for ",
127 :bright,
128 pack_name,
129 :normal,
130 " from ",
131 :underline,
132 files_url
133 ])
134 )
135
136 files = Tesla.get!(files_url).body |> Poison.decode!()
137
138 IO.puts(IO.ANSI.format(["Unpacking ", :bright, pack_name]))
139
140 pack_path =
141 Path.join([
142 Pleroma.Config.get!([:instance, :static_dir]),
143 "emoji",
144 pack_name
145 ])
146
147 files_to_unzip =
148 Enum.map(
149 files,
150 fn {_, f} -> to_charlist(f) end
151 )
152
153 {:ok, _} =
154 :zip.unzip(binary_archive,
155 cwd: pack_path,
156 file_list: files_to_unzip
157 )
158
159 IO.puts(IO.ANSI.format(["Writing emoji.txt for ", :bright, pack_name]))
160
161 emoji_txt_str =
162 Enum.map(
163 files,
164 fn {shortcode, path} ->
165 emojo_path = Path.join("/emoji/#{pack_name}", path)
166 "#{shortcode}, #{emojo_path}"
167 end
168 )
169 |> Enum.join("\n")
170
171 File.write!(Path.join(pack_path, "emoji.txt"), emoji_txt_str)
172 else
173 IO.puts(IO.ANSI.format([:bright, :red, "No pack named \"#{pack_name}\" found"]))
174 end
175 end
176 end
177
178 def run(["gen-pack", src]) do
179 Application.ensure_all_started(:hackney)
180
181 proposed_name = Path.basename(src) |> Path.rootname()
182 name = String.trim(IO.gets("Pack name [#{proposed_name}]: "))
183 # If there's no name, use the default one
184 name = if String.length(name) > 0, do: name, else: proposed_name
185
186 license = String.trim(IO.gets("License: "))
187 homepage = String.trim(IO.gets("Homepage: "))
188 description = String.trim(IO.gets("Description: "))
189
190 proposed_files_name = "#{name}.json"
191 files_name = String.trim(IO.gets("Save file list to [#{proposed_files_name}]: "))
192 files_name = if String.length(files_name) > 0, do: files_name, else: proposed_files_name
193
194 default_exts = [".png", ".gif"]
195 default_exts_str = Enum.join(default_exts, " ")
196
197 exts =
198 String.trim(
199 IO.gets("Emoji file extensions (separated with spaces) [#{default_exts_str}]: ")
200 )
201
202 exts =
203 if String.length(exts) > 0 do
204 String.split(exts, " ")
205 |> Enum.filter(fn e -> e |> String.trim() |> String.length() > 0 end)
206 else
207 default_exts
208 end
209
210 IO.puts("Downloading the pack and generating MD5")
211
212 binary_archive = Tesla.get!(src).body
213 archive_md5 = :crypto.hash(:md5, binary_archive) |> Base.encode16()
214
215 IO.puts("MD5 is #{archive_md5}")
216
217 pack_json = %{
218 name => %{
219 license: license,
220 homepage: homepage,
221 description: description,
222 src: src,
223 src_md5: archive_md5,
224 files: files_name
225 }
226 }
227
228 tmp_pack_dir = Path.join(System.tmp_dir!(), "emoji-pack-#{name}")
229
230 {:ok, _} =
231 :zip.unzip(
232 binary_archive,
233 cwd: tmp_pack_dir
234 )
235
236 emoji_map = Pleroma.Emoji.make_shortcode_to_file_map(tmp_pack_dir, exts)
237
238 File.write!(files_name, Poison.encode!(emoji_map, pretty: true))
239
240 IO.puts("""
241
242 #{files_name} has been created and contains the list of all found emojis in the pack.
243 Please review the files in the remove those not needed.
244 """)
245
246 if File.exists?("index.json") do
247 existing_data = File.read!("index.json") |> Poison.decode!()
248
249 File.write!(
250 "index.json",
251 Poison.encode!(
252 Map.merge(
253 existing_data,
254 pack_json
255 ),
256 pretty: true
257 )
258 )
259
260 IO.puts("index.json file has been update with the #{name} pack")
261 else
262 File.write!("index.json", Poison.encode!(pack_json, pretty: true))
263
264 IO.puts("index.json has been created with the #{name} pack")
265 end
266 end
267
268 defp fetch_manifest(from) do
269 Poison.decode!(
270 if String.starts_with?(from, "http") do
271 Tesla.get!(from).body
272 else
273 File.read!(from)
274 end
275 )
276 end
277
278 defp parse_global_opts(args) do
279 OptionParser.parse(
280 args,
281 strict: [
282 manifest: :string
283 ],
284 aliases: [
285 m: :manifest
286 ]
287 )
288 end
289 end