clacks/lib/clacks/worker/send_webmention.ex

111 lines
3.5 KiB
Elixir

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