defmodule Clacks.Worker.SendWebmention do use Oban.Worker, queue: :send_webmention alias Clacks.Activity alias ClacksWeb.Router.Helpers, as: Routes alias ClacksWeb.Endpoint require Logger @spec enqueue_for_activity(Activity.t()) :: :ok def enqueue_for_activity( %Activity{data: %{"type" => "Create", "object" => %{"content" => content} = note}} = activity ) do tags = Map.get(note, "tag", []) tag_hrefs = Enum.map(tags, fn %{"href" => href} -> href end) case Floki.parse_fragment(content) do {:ok, html_tree} -> # todo: should this also skip anchors with the .mention class? Floki.find(html_tree, "a") |> Enum.each(fn {"a", attrs, _} -> case Enum.find(attrs, fn {name, _} -> name == "href" end) do {"href", link} -> IO.inspect("Found link: #{link}") maybe_enqueue_for_candidate(activity, tag_hrefs, link) nil -> :ok end end) {:error, reason} -> Logger.warn("Unable to parse HTML for sending Webmentions: #{reason}") end end @spec maybe_enqueue_for_candidate(Activity.t(), [String.t()], String.t()) :: boolean() defp maybe_enqueue_for_candidate( %Activity{data: %{"object" => %{"in_reply_to" => in_reply_to}}} = activity, activity_tag_hrefs, link ) do # todo: checking in_reply_to != link won't be adequate after we support replying via Webmention to non-AP posts with false <- in_reply_to == link, false <- Enum.member?(activity_tag_hrefs, link), %URI{scheme: scheme} when scheme in ["http", "https"] <- URI.parse(link) do enqueue(activity, link) == :ok else _ -> false end end defp maybe_enqueue_for_candidate(activity, activity_tag_hrefs, link) do with false <- Enum.member?(activity_tag_hrefs, link), %URI{scheme: scheme} when scheme in ["http", "https"] <- URI.parse(link) do enqueue(activity, link) == :ok else _ -> false end end @spec enqueue(Activity.t(), String.t()) :: :ok | :error defp enqueue(activity, link) do worker = __MODULE__.new(%{activity: activity.id, link: link}) case Oban.insert(worker) do {:ok, _} -> :ok {:error, reason} -> Logger.error("Couldn't save send Webmention job: #{inspect(reason)}") :error end end @impl Oban.Worker def perform(%{"activity" => activity_id, "link" => link}, _job) do case Clacks.Webmention.Endpoint.find_endpoint(link) do nil -> :ok endpoint -> endpoint = URI.to_string(endpoint) source = Routes.activities_url(Endpoint, :get, activity_id) target = URI.encode_www_form(link) body = "source=#{source}&target=#{target}" case Clacks.HTTP.post(endpoint, body, [ {"Content-Type", "application/x-www-form-urlencoded"} ]) do {:ok, %HTTPoison.Response{status_code: status_code}} when status_code in 200..299 -> Logger.debug("Successfully sent Webmention to '#{link}'") :ok {:ok, %HTTPoison.Response{status_code: status_code}} -> Logger.error( "Unhandled status code #{status_code} for sending Webmention to '#{link}'" ) {:error, "Unhandled status code for Webmention '#{link}': #{status_code}"} {:error, error} -> Logger.error("Failed sending Webmention to '#{link}': #{inspect(error)}") {:error, error} end end end end