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