X-Git-Url: http://git.squeep.com/?a=blobdiff_plain;f=lib%2Fpleroma%2Fweb%2Frich_media%2Fparser.ex;h=5727fda189bf39f4ac501461504ff247f1f86fa7;hb=0a05670b4499d170f6bc0eaecfae0d71be022227;hp=ba8dc6f2a4b0812a9d3a715ce36deefdd921b215;hpb=18234cc44e6bc989e3e3cf15714c54b4fa05b9dd;p=akkoma diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex index ba8dc6f2a..5727fda18 100644 --- a/lib/pleroma/web/rich_media/parser.ex +++ b/lib/pleroma/web/rich_media/parser.ex @@ -1,14 +1,9 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors +# Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.RichMedia.Parser do - @hackney_options [ - pool: :media, - recv_timeout: 2_000, - max_body: 2_000_000, - with_body: true - ] + require Logger defp parsers do Pleroma.Config.get([:rich_media, :parsers]) @@ -17,17 +12,34 @@ defmodule Pleroma.Web.RichMedia.Parser do def parse(nil), do: {:error, "No URL provided"} if Pleroma.Config.get(:env) == :test do + @spec parse(String.t()) :: {:ok, map()} | {:error, any()} def parse(url), do: parse_url(url) else + @spec parse(String.t()) :: {:ok, map()} | {:error, any()} def parse(url) do - try do - Cachex.fetch!(:rich_media_cache, url, fn _ -> - {:commit, parse_url(url)} - end) - |> set_ttl_based_on_image(url) - rescue - e -> - {:error, "Cachex error: #{inspect(e)}"} + with {:ok, data} <- get_cached_or_parse(url), + {:ok, _} <- set_ttl_based_on_image(data, url) do + {:ok, data} + else + {:error, {:invalid_metadata, data}} = e -> + Logger.debug(fn -> "Incomplete or invalid metadata for #{url}: #{inspect(data)}" end) + e + + error -> + Logger.error(fn -> "Rich media error for #{url}: #{inspect(error)}" end) + error + end + end + + defp get_cached_or_parse(url) do + case Cachex.fetch!(:rich_media_cache, url, fn _ -> {:commit, parse_url(url)} end) do + {:ok, _data} = res -> + res + + {:error, _} = e -> + ttl = Pleroma.Config.get([:rich_media, :failure_backoff], 60_000) + Cachex.expire(:rich_media_cache, url, ttl) + e end end end @@ -35,17 +47,17 @@ defmodule Pleroma.Web.RichMedia.Parser do @doc """ Set the rich media cache based on the expiration time of image. - Define a module that has `run` function + Adopt behaviour `Pleroma.Web.RichMedia.Parser.TTL` ## Example defmodule MyModule do - def run(data, url) do + @behaviour Pleroma.Web.RichMedia.Parser.TTL + def ttl(data, url) do image_url = Map.get(data, :image) # do some parsing in the url and get the ttl of the image - # ttl is unix time - ttl = parse_ttl_from_url(image_url) - Cachex.expire_at(:rich_media_cache, url, ttl * 1000) + # and return ttl is unix time + parse_ttl_from_url(image_url) end end @@ -54,63 +66,68 @@ defmodule Pleroma.Web.RichMedia.Parser do config :pleroma, :rich_media, ttl_setters: [MyModule] """ - def set_ttl_based_on_image({:ok, data}, url) do - case Cachex.ttl(:rich_media_cache, url) do - {:ok, nil} -> - modules = Pleroma.Config.get([:rich_media, :ttl_setters]) - - if Enum.count(modules) > 0 do - Enum.each(modules, & &1.run(data, url)) + @spec set_ttl_based_on_image(map(), String.t()) :: + {:ok, Integer.t() | :noop} | {:error, :no_key} + def set_ttl_based_on_image(data, url) do + case get_ttl_from_image(data, url) do + {:ok, ttl} when is_number(ttl) -> + ttl = ttl * 1000 + + case Cachex.expire_at(:rich_media_cache, url, ttl) do + {:ok, true} -> {:ok, ttl} + {:ok, false} -> {:error, :no_key} end - {:ok, data} - _ -> - {:ok, data} + {:ok, :noop} end end - def set_ttl_based_on_image(data, _url), do: data + defp get_ttl_from_image(data, url) do + [:rich_media, :ttl_setters] + |> Pleroma.Config.get() + |> Enum.reduce({:ok, nil}, fn + module, {:ok, _ttl} -> + module.ttl(data, url) - defp parse_url(url) do - try do - {:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: @hackney_options) + _, error -> + error + end) + end + def parse_url(url) do + with {:ok, %Tesla.Env{body: html}} <- Pleroma.Web.RichMedia.Helpers.rich_media_get(url), + {:ok, html} <- Floki.parse_document(html) do html |> maybe_parse() + |> Map.put("url", url) |> clean_parsed_data() |> check_parsed_data() - rescue - e -> - {:error, "Parsing error: #{inspect(e)}"} end end defp maybe_parse(html) do Enum.reduce_while(parsers(), %{}, fn parser, acc -> case parser.parse(html, acc) do - {:ok, data} -> {:halt, data} - {:error, _msg} -> {:cont, acc} + data when data != %{} -> {:halt, data} + _ -> {:cont, acc} end end) end - defp check_parsed_data(%{title: title} = data) when is_binary(title) and byte_size(title) > 0 do + defp check_parsed_data(%{"title" => title} = data) + when is_binary(title) and title != "" do {:ok, data} end defp check_parsed_data(data) do - {:error, "Found metadata was invalid or incomplete: #{inspect(data)}"} + {:error, {:invalid_metadata, data}} end defp clean_parsed_data(data) do data |> Enum.reject(fn {key, val} -> - with {:ok, _} <- Jason.encode(%{key => val}) do - false - else - _ -> true - end + not match?({:ok, _}, Jason.encode(%{key => val})) end) |> Map.new() end