emoji.ex 6.84 KB
Newer Older
1
# Pleroma: A lightweight social networking server
2
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3
4
5
6
# SPDX-License-Identifier: AGPL-3.0-only

defmodule Mix.Tasks.Pleroma.Emoji do
  use Mix.Task
Alexander Strizhakov's avatar
Alexander Strizhakov committed
7
  import Mix.Pleroma
8

vaartis's avatar
vaartis committed
9
  @shortdoc "Manages emoji packs"
10
  @moduledoc File.read!("docs/administration/CLI_tasks/emoji.md")
11

12
  def run(["ls-packs" | args]) do
Alexander Strizhakov's avatar
Alexander Strizhakov committed
13
    start_pleroma()
14

15
16
    {options, [], []} = parse_global_opts(args)

17
18
    url_or_path = options[:manifest] || default_manifest()
    manifest = fetch_manifest(url_or_path)
19
20
21
22
23
24
25
26
27
28
29
30
31

    Enum.each(manifest, fn {name, info} ->
      to_print = [
        {"Name", name},
        {"Homepage", info["homepage"]},
        {"Description", info["description"]},
        {"License", info["license"]},
        {"Source", info["src"]}
      ]

      for {param, value} <- to_print do
        IO.puts(IO.ANSI.format([:bright, param, :normal, ": ", value]))
      end
32
33
34

      # A newline
      IO.puts("")
35
36
37
    end)
  end

38
  def run(["get-packs" | args]) do
Alexander Strizhakov's avatar
Alexander Strizhakov committed
39
    start_pleroma()
40

41
42
    {options, pack_names, []} = parse_global_opts(args)

43
    url_or_path = options[:manifest] || default_manifest()
44

45
    manifest = fetch_manifest(url_or_path)
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

    for pack_name <- pack_names do
      if Map.has_key?(manifest, pack_name) do
        pack = manifest[pack_name]
        src_url = pack["src"]

        IO.puts(
          IO.ANSI.format([
            "Downloading ",
            :bright,
            pack_name,
            :normal,
            " from ",
            :underline,
            src_url
          ])
62
63
        )

64
        binary_archive = Tesla.get!(client(), src_url).body
65
        archive_sha = :crypto.hash(:sha256, binary_archive) |> Base.encode16()
66

67
        sha_status_text = ["SHA256 of ", :bright, pack_name, :normal, " source file is ", :bright]
vaartis's avatar
vaartis committed
68

69
70
        if archive_sha == String.upcase(pack["src_sha256"]) do
          IO.puts(IO.ANSI.format(sha_status_text ++ [:green, "OK"]))
71
        else
72
          IO.puts(IO.ANSI.format(sha_status_text ++ [:red, "BAD"]))
73

74
          raise "Bad SHA256 for #{pack_name}"
75
        end
76

77
        # The url specified in files should be in the same directory
78
79
80
81
        files_url =
          url_or_path
          |> Path.dirname()
          |> Path.join(pack["files"])
82
83
84
85
86
87
88
89
90
91
92
93
94

        IO.puts(
          IO.ANSI.format([
            "Fetching the file list for ",
            :bright,
            pack_name,
            :normal,
            " from ",
            :underline,
            files_url
          ])
        )

feld's avatar
feld committed
95
        files = Tesla.get!(client(), files_url).body |> Jason.decode!()
96

97
98
99
100
101
102
103
104
105
106
107
        IO.puts(IO.ANSI.format(["Unpacking ", :bright, pack_name]))

        pack_path =
          Path.join([
            Pleroma.Config.get!([:instance, :static_dir]),
            "emoji",
            pack_name
          ])

        files_to_unzip =
          Enum.map(
108
            files,
109
110
111
112
113
114
115
116
117
            fn {_, f} -> to_charlist(f) end
          )

        {:ok, _} =
          :zip.unzip(binary_archive,
            cwd: pack_path,
            file_list: files_to_unzip
          )

118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
        IO.puts(IO.ANSI.format(["Writing pack.json for ", :bright, pack_name]))

        pack_json = %{
          pack: %{
            "license" => pack["license"],
            "homepage" => pack["homepage"],
            "description" => pack["description"],
            "fallback-src" => pack["src"],
            "fallback-src-sha256" => pack["src_sha256"],
            "share-files" => true
          },
          files: files
        }

        File.write!(Path.join(pack_path, "pack.json"), Jason.encode!(pack_json, pretty: true))
133
134
135
      else
        IO.puts(IO.ANSI.format([:bright, :red, "No pack named \"#{pack_name}\" found"]))
      end
136
137
    end
  end
138

139
  def run(["gen-pack" | args]) do
Alexander Strizhakov's avatar
Alexander Strizhakov committed
140
    start_pleroma()
vaartis's avatar
vaartis committed
141

142
143
144
145
146
147
148
149
150
151
152
153
    {opts, [src], []} =
      OptionParser.parse(
        args,
        strict: [
          name: :string,
          license: :string,
          homepage: :string,
          description: :string,
          files: :string,
          extensions: :string
        ]
      )
vaartis's avatar
vaartis committed
154

155
156
157
158
159
    proposed_name = Path.basename(src) |> Path.rootname()
    name = get_option(opts, :name, "Pack name:", proposed_name)
    license = get_option(opts, :license, "License:")
    homepage = get_option(opts, :homepage, "Homepage:")
    description = get_option(opts, :description, "Description:")
vaartis's avatar
vaartis committed
160

161
162
    proposed_files_name = "#{name}_files.json"
    files_name = get_option(opts, :files, "Save file list to:", proposed_files_name)
vaartis's avatar
vaartis committed
163
164

    default_exts = [".png", ".gif"]
vaartis's avatar
vaartis committed
165

166
167
168
169
170
171
    custom_exts =
      get_option(
        opts,
        :extensions,
        "Emoji file extensions (separated with spaces):",
        Enum.join(default_exts, " ")
vaartis's avatar
vaartis committed
172
      )
173
      |> String.split(" ", trim: true)
vaartis's avatar
vaartis committed
174

vaartis's avatar
vaartis committed
175
    exts =
176
      if MapSet.equal?(MapSet.new(default_exts), MapSet.new(custom_exts)) do
vaartis's avatar
vaartis committed
177
        default_exts
178
179
      else
        custom_exts
vaartis's avatar
vaartis committed
180
181
      end

182
183
    IO.puts("Using #{Enum.join(exts, " ")} extensions")

184
    IO.puts("Downloading the pack and generating SHA256")
vaartis's avatar
vaartis committed
185

186
    binary_archive = Tesla.get!(client(), src).body
187
    archive_sha = :crypto.hash(:sha256, binary_archive) |> Base.encode16()
vaartis's avatar
vaartis committed
188

189
    IO.puts("SHA256 is #{archive_sha}")
vaartis's avatar
vaartis committed
190
191
192
193
194
195
196

    pack_json = %{
      name => %{
        license: license,
        homepage: homepage,
        description: description,
        src: src,
197
        src_sha256: archive_sha,
vaartis's avatar
vaartis committed
198
199
200
201
202
        files: files_name
      }
    }

    tmp_pack_dir = Path.join(System.tmp_dir!(), "emoji-pack-#{name}")
vaartis's avatar
vaartis committed
203

minibikini's avatar
minibikini committed
204
    {:ok, _} = :zip.unzip(binary_archive, cwd: String.to_charlist(tmp_pack_dir))
vaartis's avatar
vaartis committed
205

206
    emoji_map = Pleroma.Emoji.Loader.make_shortcode_to_file_map(tmp_pack_dir, exts)
207

feld's avatar
feld committed
208
    File.write!(files_name, Jason.encode!(emoji_map, pretty: true))
vaartis's avatar
vaartis committed
209

vaartis's avatar
vaartis committed
210
    IO.puts("""
vaartis's avatar
vaartis committed
211
212

    #{files_name} has been created and contains the list of all found emojis in the pack.
213
    Please review the files in the pack and remove those not needed.
vaartis's avatar
vaartis committed
214
    """)
vaartis's avatar
vaartis committed
215

216
217
218
219
    pack_file = "#{name}.json"

    if File.exists?(pack_file) do
      existing_data = File.read!(pack_file) |> Jason.decode!()
vaartis's avatar
vaartis committed
220
221

      File.write!(
222
        pack_file,
feld's avatar
feld committed
223
        Jason.encode!(
vaartis's avatar
vaartis committed
224
225
226
227
228
229
230
231
          Map.merge(
            existing_data,
            pack_json
          ),
          pretty: true
        )
      )

232
      IO.puts("#{pack_file} has been updated with the #{name} pack")
vaartis's avatar
vaartis committed
233
    else
234
      File.write!(pack_file, Jason.encode!(pack_json, pretty: true))
vaartis's avatar
vaartis committed
235

236
      IO.puts("#{pack_file} has been created with the #{name} pack")
vaartis's avatar
vaartis committed
237
238
239
    end
  end

240
  defp fetch_manifest(from) do
feld's avatar
feld committed
241
    Jason.decode!(
242
      if String.starts_with?(from, "http") do
243
        Tesla.get!(client(), from).body
244
245
246
247
      else
        File.read!(from)
      end
    )
248
249
250
251
252
253
254
255
256
257
258
259
260
  end

  defp parse_global_opts(args) do
    OptionParser.parse(
      args,
      strict: [
        manifest: :string
      ],
      aliases: [
        m: :manifest
      ]
    )
  end
261
262
263
264
265
266
267
268

  defp client do
    middleware = [
      {Tesla.Middleware.FollowRedirects, [max_redirects: 3]}
    ]

    Tesla.client(middleware)
  end
269
270

  defp default_manifest, do: Pleroma.Config.get!([:emoji, :default_manifest])
271
end