9cb6fb88dddab1a58bf00823884bfafa3f486f44
[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 Pleroma instance"
9 @moduledoc """
10 """
11
12 @default_manifest "https://git.pleroma.social/vaartis/emoji-index/raw/master/index.json"
13
14 def run(["ls-packs" | args]) do
15 Application.ensure_all_started(:hackney)
16
17 {options, [], []} = parse_global_opts(args)
18
19 manifest =
20 fetch_manifest(if options[:manifest], do: options[:manifest], else: @default_manifest)
21
22 Enum.each(manifest, fn {name, info} ->
23 to_print = [
24 {"Name", name},
25 {"Homepage", info["homepage"]},
26 {"Description", info["description"]},
27 {"License", info["license"]},
28 {"Source", info["src"]}
29 ]
30
31 for {param, value} <- to_print do
32 IO.puts(IO.ANSI.format([:bright, param, :normal, ": ", value]))
33 end
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!(src_url).body
64 archive_md5 = :crypto.hash(:md5, binary_archive) |> Base.encode16()
65
66 md5_status_text = ["MD5 of ", :bright, pack_name, :normal, " source file is ", :bright]
67
68 if archive_md5 == String.upcase(pack["src_md5"]) do
69 IO.puts(IO.ANSI.format(md5_status_text ++ [:green, "OK"]))
70 else
71 IO.puts(IO.ANSI.format(md5_status_text ++ [:red, "BAD"]))
72
73 raise "Bad MD5 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!(files_url).body |> Poison.decode!()
92
93 IO.puts(IO.ANSI.format(["Unpacking ", :bright, pack_name]))
94
95 static_path = Path.join(:code.priv_dir(:pleroma), "static")
96
97 pack_path =
98 Path.join([
99 static_path,
100 Pleroma.Config.get!([:instance, :static_dir]),
101 "emoji",
102 pack_name
103 ])
104
105 files_to_unzip =
106 Enum.map(
107 files,
108 fn {_, f} -> to_charlist(f) end
109 )
110
111 {:ok, _} =
112 :zip.unzip(binary_archive,
113 cwd: pack_path,
114 file_list: files_to_unzip
115 )
116
117 IO.puts(IO.ANSI.format(["Writing emoji.txt for ", :bright, pack_name]))
118
119 common_pack_path =
120 Path.join([
121 "/",
122 Pleroma.Config.get!([:instance, :static_dir]),
123 "emoji",
124 pack_name
125 ])
126
127 emoji_txt_str =
128 Enum.map(
129 files,
130 fn {shortcode, path} ->
131 "#{shortcode}, #{Path.join(common_pack_path, path)}"
132 end
133 )
134 |> Enum.join("\n")
135
136 File.write!(Path.join(pack_path, "emoji.txt"), emoji_txt_str)
137 else
138 IO.puts(IO.ANSI.format([:bright, :red, "No pack named \"#{pack_name}\" found"]))
139 end
140 end
141 end
142
143 def run(["gen-pack", src]) do
144 Application.ensure_all_started(:hackney)
145
146 proposed_name = Path.basename(src) |> Path.rootname()
147 name = String.trim(IO.gets("Pack name [#{proposed_name}]: "))
148 # If there's no name, use the default one
149 name = if String.length(name) > 0, do: name, else: proposed_name
150
151 license = String.trim(IO.gets("License: "))
152 homepage = String.trim(IO.gets("Homepage: "))
153 description = String.trim(IO.gets("Description: "))
154
155 proposed_files_name = "#{name}.json"
156 files_name = String.trim(IO.gets("Save file list to [#{proposed_files_name}]: "))
157 files_name = if String.length(files_name) > 0, do: files_name, else: proposed_files_name
158
159 default_exts = [".png", ".gif"]
160 default_exts_str = Enum.join(default_exts, " ")
161
162 exts =
163 String.trim(
164 IO.gets("Emoji file extensions (separated with spaces) [#{default_exts_str}]: ")
165 )
166
167 exts =
168 if String.length(exts) > 0 do
169 String.split(exts, " ")
170 |> Enum.filter(fn e -> e |> String.trim() |> String.length() > 0 end)
171 else
172 default_exts
173 end
174
175 IO.puts("Downloading the pack and generating MD5")
176
177 binary_archive = Tesla.get!(src).body
178 archive_md5 = :crypto.hash(:md5, binary_archive) |> Base.encode16()
179
180 IO.puts("MD5 is #{archive_md5}")
181
182 pack_json = %{
183 name => %{
184 license: license,
185 homepage: homepage,
186 description: description,
187 src: src,
188 src_md5: archive_md5,
189 files: files_name
190 }
191 }
192
193 tmp_pack_dir = Path.join(System.tmp_dir!(), "emoji-pack-#{name}")
194
195 {:ok, _} =
196 :zip.unzip(
197 binary_archive,
198 cwd: tmp_pack_dir
199 )
200
201 emoji_map = Pleroma.Emoji.make_shortcode_to_file_map(tmp_pack_dir, exts)
202
203 File.write!(files_name, Poison.encode!(emoji_map, pretty: true))
204
205 IO.puts("""
206
207 #{files_name} has been created and contains the list of all found emojis in the pack.
208 Please review the files in the remove those not needed.
209 """)
210
211 if File.exists?("index.json") do
212 existing_data = File.read!("index.json") |> Poison.decode!()
213
214 File.write!(
215 "index.json",
216 Poison.encode!(
217 Map.merge(
218 existing_data,
219 pack_json
220 ),
221 pretty: true
222 )
223 )
224
225 IO.puts("index.json file has been update with the #{name} pack")
226 else
227 File.write!("index.json", Poison.encode!(pack_json, pretty: true))
228
229 IO.puts("index.json has been created with the #{name} pack")
230 end
231 end
232
233 defp fetch_manifest(from) do
234 Poison.decode!(
235 if String.starts_with?(from, "http") do
236 Tesla.get!(from).body
237 else
238 File.read!(from)
239 end
240 )
241 end
242
243 defp parse_global_opts(args) do
244 OptionParser.parse(
245 args,
246 strict: [
247 manifest: :string
248 ],
249 aliases: [
250 m: :manifest
251 ]
252 )
253 end
254 end