Compare commits
2 Commits
0f7eb99efb
...
de7a1e617d
Author | SHA1 | Date |
---|---|---|
Shadowfacts | de7a1e617d | |
Shadowfacts | 7e38b460c6 |
|
@ -38,10 +38,12 @@ config :http_signatures, adapter: Clacks.SignatureAdapter
|
|||
config :clacks, Oban,
|
||||
repo: Clacks.Repo,
|
||||
prune: {:maxlen, 10_000},
|
||||
queues: [federate: 10]
|
||||
queues: [federate: 5, send_webmention: 1]
|
||||
|
||||
config :floki, :html_parser, Floki.HTMLParser.FastHtml
|
||||
|
||||
config :clacks, :send_webmentions, true
|
||||
|
||||
# Import environment specific config. This must remain at the bottom
|
||||
# of this file so it overrides the configuration defined above.
|
||||
import_config "#{Mix.env()}.exs"
|
||||
|
|
|
@ -13,13 +13,26 @@ defmodule Clacks.ActivityPub.Helper do
|
|||
:error
|
||||
|
||||
{:ok, activity} ->
|
||||
if Application.get_env(:clacks, :send_webmentions, true) do
|
||||
Clacks.Worker.SendWebmention.enqueue_for_activity(activity)
|
||||
end
|
||||
|
||||
case enqueue_federate_job(activity, actor) do
|
||||
:ok -> {:ok, activity}
|
||||
:error -> :error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@spec enqueue_federate_job(Activity.t(), Actor.t()) :: :ok | :error
|
||||
defp enqueue_federate_job(activity, actor) do
|
||||
worker =
|
||||
%{id: activity.id, actor_id: actor.id}
|
||||
|> Clacks.Worker.Federate.new()
|
||||
|
||||
case Oban.insert(worker) do
|
||||
{:ok, _} ->
|
||||
{:ok, activity}
|
||||
:ok
|
||||
|
||||
{:error, changeset} ->
|
||||
Logger.error("Couldn't save federate job: #{inspect(changeset)}")
|
||||
|
@ -27,4 +40,3 @@ defmodule Clacks.ActivityPub.Helper do
|
|||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
defmodule Clacks.HTTP do
|
||||
require Logger
|
||||
|
||||
@spec get(url :: String.t(), headers :: [{String.t(), String.t()}]) ::
|
||||
@spec get(url :: String.t(), headers :: HTTPoison.headers()) ::
|
||||
{:ok, HTTPoison.Response.t()} | {:error, String.t()}
|
||||
def get(url, headers \\ []) do
|
||||
fetch(:get, url, headers)
|
||||
end
|
||||
|
||||
@spec head(url :: String.t(), headers :: [{String.t(), String.t()}]) ::
|
||||
@spec head(url :: String.t(), headers :: HTTPoison.headers()) ::
|
||||
{:ok, HTTPoison.Response.t()} | {:error, String.t()}
|
||||
def head(url, headers \\ []) do
|
||||
fetch(:head, url, headers)
|
||||
|
@ -27,7 +27,9 @@ defmodule Clacks.HTTP do
|
|||
|> Enum.find(fn {name, _value} -> String.downcase(name) == "location" end)
|
||||
|> case do
|
||||
{_, new_url} ->
|
||||
new_url = URI.merge(URI.parse(url), URI.parse(new_url))
|
||||
new_url =
|
||||
URI.merge(URI.parse(url), URI.parse(new_url))
|
||||
|> URI.to_string()
|
||||
|
||||
Logger.debug("Got 301 redirect from #{url} to #{new_url}")
|
||||
fetch(method, new_url, headers)
|
||||
|
@ -49,4 +51,10 @@ defmodule Clacks.HTTP do
|
|||
{:error, inspect(reason)}
|
||||
end
|
||||
end
|
||||
|
||||
@spec post(String.t(), any(), HTTPoison.headers()) ::
|
||||
{:ok, HTTPoison.Response.t()} | {:error, HTTPoison.Error.t()}
|
||||
def post(url, body, headers \\ []) do
|
||||
HTTPoison.post(url, body, headers)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -118,7 +118,7 @@ defmodule Clacks.UserActionsHelper do
|
|||
{String.replace(content, ["\r\n", "\n"], "<br>"), mentions}
|
||||
end
|
||||
|
||||
@link_regex ~r/\bhttps?\S+\b/i
|
||||
@link_regex ~r/\b(https?\S+)\b/i
|
||||
|
||||
defp replace_links(content) do
|
||||
Regex.replace(@link_regex, content, "<a href=\"\\1\">\\1</a>")
|
||||
|
|
|
@ -56,7 +56,7 @@ defmodule Clacks.Webmention.Endpoint do
|
|||
uri_reference =
|
||||
value
|
||||
|> String.trim()
|
||||
|> String.slice(1..-1)
|
||||
|> String.slice(1..-2)
|
||||
|
||||
{_, rel} =
|
||||
params
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
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
|
|
@ -4,11 +4,9 @@ defmodule ClacksWeb.ActivitiesController do
|
|||
alias ClacksWeb.Router.Helpers, as: Routes
|
||||
alias ClacksWeb.Endpoint
|
||||
|
||||
def get(conn, _params) do
|
||||
ap_id = current_url(conn, %{})
|
||||
|
||||
case Activity.get_by_ap_id(ap_id) do
|
||||
%Activity{local: true, id: id, data: data} ->
|
||||
def get(conn, %{"id" => id}) do
|
||||
case Activity.get(id) do
|
||||
%Activity{local: true, data: data} ->
|
||||
case conn.assigns[:format] do
|
||||
"activity+json" ->
|
||||
json(conn, data)
|
||||
|
@ -29,16 +27,4 @@ defmodule ClacksWeb.ActivitiesController do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
def get_status(conn, %{"id" => status_id}) do
|
||||
case Activity.get(status_id) do
|
||||
%Activity{local: true, data: data} ->
|
||||
json(conn, data)
|
||||
|
||||
_ ->
|
||||
conn
|
||||
|> put_status(404)
|
||||
|> json(%{error: "Not Found"})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
<div class="<%= assigns[:class] || "status h-entry" %>">
|
||||
<div class="status-meta">
|
||||
<div class="p-author h-card">
|
||||
<h2 class="status-author-nickname">
|
||||
<a href="<%= local_actor_link(@author) %>" class="p-author">
|
||||
<a href="<%= local_actor_link(@author) %>">
|
||||
<%= @author.data["preferredUsername"] %>
|
||||
</a>
|
||||
</h2>
|
||||
<h3 class="status-author-username">
|
||||
<a href="<%= @author.ap_id %>">
|
||||
<a href="<%= @author.ap_id %>" class="p-name u-url">
|
||||
<%= display_username(@author) %>
|
||||
</a>
|
||||
</h3>
|
||||
</div>
|
||||
<p class="status-meta-right">
|
||||
<time datetime="<%= @note["published"] %>" class="dt-published"><%= display_timestamp(@note["published"]) %></time>
|
||||
<a href="<%= @note["url"] || @note["id"] %>" class="status-permalink u-url">Permalink</a>
|
||||
|
|
Loading…
Reference in New Issue