111 lines
3.5 KiB
Elixir
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
|