+ @spec list_remote(String.t()) :: {:ok, map()} | {:error, atom()}
+ def list_remote(url) do
+ uri = url |> String.trim() |> URI.parse()
+
+ with :ok <- validate_shareable_packs_available(uri) do
+ uri
+ |> URI.merge("/api/pleroma/emoji/packs")
+ |> http_get()
+ end
+ end
+
+ @spec list_local() :: {:ok, map()}
+ def list_local do
+ with {:ok, results} <- list_packs_dir() do
+ packs =
+ results
+ |> Enum.map(fn name ->
+ case load_pack(name) do
+ {:ok, pack} -> pack
+ _ -> nil
+ end
+ end)
+ |> Enum.reject(&is_nil/1)
+ |> Map.new(fn pack -> {pack.name, validate_pack(pack)} end)
+
+ {:ok, packs}
+ end
+ end
+
+ @spec get_archive(String.t()) :: {:ok, binary()} | {:error, atom()}
+ def get_archive(name) do
+ with {:ok, pack} <- load_pack(name),
+ :ok <- validate_downloadable(pack) do
+ {:ok, fetch_archive(pack)}
+ end
+ end
+
+ @spec download(String.t(), String.t(), String.t()) :: :ok | {:error, atom()}
+ def download(name, url, as) do
+ uri = url |> String.trim() |> URI.parse()
+
+ with :ok <- validate_shareable_packs_available(uri),
+ {:ok, remote_pack} <- uri |> URI.merge("/api/pleroma/emoji/packs/#{name}") |> http_get(),
+ {:ok, %{sha: sha, url: url} = pack_info} <- fetch_pack_info(remote_pack, uri, name),
+ {:ok, archive} <- download_archive(url, sha),
+ pack <- copy_as(remote_pack, as || name),
+ {:ok, _} = unzip(archive, pack_info, remote_pack, pack) do
+ # Fallback can't contain a pack.json file, since that would cause the fallback-src-sha256
+ # in it to depend on itself
+ if pack_info[:fallback] do
+ save_pack(pack)
+ else
+ {:ok, pack}
+ end
+ end
+ end
+
+ @spec save_metadata(map(), t()) :: {:ok, t()} | {:error, File.posix()}
+ def save_metadata(metadata, %__MODULE__{} = pack) do
+ pack
+ |> Map.put(:pack, metadata)
+ |> save_pack()
+ end
+
+ @spec update_metadata(String.t(), map()) :: {:ok, t()} | {:error, File.posix()}
+ def update_metadata(name, data) do
+ with {:ok, pack} <- load_pack(name) do
+ if fallback_sha_changed?(pack, data) do
+ update_sha_and_save_metadata(pack, data)
+ else
+ save_metadata(data, pack)
+ end
+ end
+ end
+
+ @spec load_pack(String.t()) :: {:ok, t()} | {:error, :not_found}
+ def load_pack(name) do
+ pack_file = Path.join([emoji_path(), name, "pack.json"])
+
+ if File.exists?(pack_file) do
+ pack =
+ pack_file
+ |> File.read!()
+ |> from_json()
+ |> Map.put(:pack_file, pack_file)
+ |> Map.put(:path, Path.dirname(pack_file))
+ |> Map.put(:name, name)
+
+ {:ok, pack}
+ else
+ {:error, :not_found}
+ end
+ end
+
+ @spec emoji_path() :: Path.t()
+ defp emoji_path do
+ [:instance, :static_dir]
+ |> Pleroma.Config.get!()
+ |> Path.join("emoji")
+ end
+
+ defp validate_emoji_not_exists(shortcode, force \\ false)
+ defp validate_emoji_not_exists(_shortcode, true), do: :ok
+
+ defp validate_emoji_not_exists(shortcode, _) do
+ case Emoji.get(shortcode) do
+ nil -> :ok
+ _ -> {:error, :already_exists}
+ end
+ end
+