Merge branch 'recipients-inline' into 'develop'

ForceMentionsInContent: wrap mentions in a span, fix the formatting

See merge request pleroma/pleroma!3620
This commit is contained in:
Alex Gleason 2022-01-25 17:43:39 +00:00
commit 0f4e0e667e
3 changed files with 104 additions and 30 deletions

View file

@ -4,6 +4,7 @@
defmodule Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent do defmodule Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent do
alias Pleroma.Formatter alias Pleroma.Formatter
alias Pleroma.Object
alias Pleroma.User alias Pleroma.User
@behaviour Pleroma.Web.ActivityPub.MRF.Policy @behaviour Pleroma.Web.ActivityPub.MRF.Policy
@ -34,31 +35,50 @@ defp extract_mention_uris_from_content(content) do
do_extract(tree, []) do_extract(tree, [])
end end
defp get_replied_to_user(%{"inReplyTo" => in_reply_to}) do
case Object.normalize(in_reply_to, fetch: false) do
%Object{data: %{"actor" => actor}} -> User.get_cached_by_ap_id(actor)
_ -> nil
end
end
defp get_replied_to_user(_object), do: nil
# Ensure the replied-to user is sorted to the left
defp sort_replied_user([%User{id: user_id} | _] = users, %User{id: user_id}), do: users
defp sort_replied_user(users, %User{id: user_id} = user) do
if Enum.find(users, fn u -> u.id == user_id end) do
users = Enum.reject(users, fn u -> u.id == user_id end)
[user | users]
else
users
end
end
defp sort_replied_user(users, _), do: users
@impl true @impl true
def filter(%{"type" => "Create", "object" => %{"type" => "Note", "tag" => tag}} = object) do def filter(%{"type" => "Create", "object" => %{"type" => "Note", "to" => to}} = object)
when is_list(to) do
# image-only posts from pleroma apparently reach this MRF without the content field # image-only posts from pleroma apparently reach this MRF without the content field
content = object["object"]["content"] || "" content = object["object"]["content"] || ""
# Get the replied-to user for sorting
replied_to_user = get_replied_to_user(object["object"])
mention_users = mention_users =
tag to
|> Enum.filter(fn tag -> tag["type"] == "Mention" end) |> Enum.map(&User.get_cached_by_ap_id/1)
|> Enum.map(& &1["href"])
|> Enum.reject(&is_nil/1) |> Enum.reject(&is_nil/1)
|> Enum.map(fn ap_id_or_uri -> |> sort_replied_user(replied_to_user)
case User.get_or_fetch_by_ap_id(ap_id_or_uri) do
{:ok, user} -> {ap_id_or_uri, user}
_ -> {ap_id_or_uri, User.get_by_uri(ap_id_or_uri)}
end
end)
|> Enum.reject(fn {_, user} -> user == nil end)
|> Enum.into(%{})
explicitly_mentioned_uris = extract_mention_uris_from_content(content) explicitly_mentioned_uris = extract_mention_uris_from_content(content)
added_mentions = added_mentions =
Enum.reduce(mention_users, "", fn {uri, user}, acc -> Enum.reduce(mention_users, "", fn %User{ap_id: uri} = user, acc ->
unless uri in explicitly_mentioned_uris do unless uri in explicitly_mentioned_uris do
acc <> Formatter.mention_from_user(user) acc <> Formatter.mention_from_user(user, %{mentions_format: :compact}) <> " "
else else
acc acc
end end
@ -66,7 +86,7 @@ def filter(%{"type" => "Create", "object" => %{"type" => "Note", "tag" => tag}}
content = content =
if added_mentions != "", if added_mentions != "",
do: added_mentions <> " " <> content, do: "<span class=\"recipients-inline\">#{added_mentions}</span>" <> content,
else: content else: content
{:ok, put_in(object["object"]["content"], content)} {:ok, put_in(object["object"]["content"], content)}

View file

@ -56,7 +56,7 @@ defmodule Pleroma.HTML.Scrubber.Default do
Meta.allow_tag_with_these_attributes(:u, []) Meta.allow_tag_with_these_attributes(:u, [])
Meta.allow_tag_with_these_attributes(:ul, []) Meta.allow_tag_with_these_attributes(:ul, [])
Meta.allow_tag_with_this_attribute_values(:span, "class", ["h-card"]) Meta.allow_tag_with_this_attribute_values(:span, "class", ["h-card", "recipients-inline"])
Meta.allow_tag_with_these_attributes(:span, []) Meta.allow_tag_with_these_attributes(:span, [])
Meta.allow_tag_with_this_attribute_values(:code, "class", ["inline"]) Meta.allow_tag_with_this_attribute_values(:code, "class", ["inline"])

View file

@ -3,22 +3,40 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.ForceMentionsInContentTest do defmodule Pleroma.Web.ActivityPub.MRF.ForceMentionsInContentTest do
alias Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent
import Pleroma.Factory
use Pleroma.DataCase use Pleroma.DataCase
require Pleroma.Constants
alias Pleroma.Constants
alias Pleroma.Object
alias Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent
alias Pleroma.Web.CommonAPI
import Pleroma.Factory
test "adds mentions to post content" do test "adds mentions to post content" do
users = %{ [lain, coolboymew, dielan, hakui, fence] = [
"lain@lain.com" => "https://lain.com/users/lain", insert(:user, ap_id: "https://lain.com/users/lain", nickname: "lain@lain.com", local: false),
"coolboymew@shitposter.club" => "https://shitposter.club/users/coolboymew", insert(:user,
"dielan@shitposter.club" => "https://shitposter.club/users/dielan", ap_id: "https://shitposter.club/users/coolboymew",
"hakui@tuusin.misono-ya.info" => "https://tuusin.misono-ya.info/users/hakui", nickname: "coolboymew@shitposter.club",
"fence@xyzzy.link" => "https://xyzzy.link/users/fence" local: false
} ),
insert(:user,
Enum.each(users, fn {nickname, ap_id} -> ap_id: "https://shitposter.club/users/dielan",
insert(:user, ap_id: ap_id, nickname: nickname, local: false) nickname: "dielan@shitposter.club",
end) local: false
),
insert(:user,
ap_id: "https://tuusin.misono-ya.info/users/hakui",
nickname: "hakui@tuusin.misono-ya.info",
local: false
),
insert(:user,
ap_id: "https://xyzzy.link/users/fence",
nickname: "fence@xyzzy.link",
local: false
)
]
object = File.read!("test/fixtures/soapbox_no_mentions_in_content.json") |> Jason.decode!() object = File.read!("test/fixtures/soapbox_no_mentions_in_content.json") |> Jason.decode!()
@ -29,6 +47,42 @@ test "adds mentions to post content" do
} }
{:ok, %{"object" => %{"content" => filtered}}} = ForceMentionsInContent.filter(activity) {:ok, %{"object" => %{"content" => filtered}}} = ForceMentionsInContent.filter(activity)
Enum.each(users, fn {nickname, _} -> assert filtered =~ nickname end)
assert filtered ==
"<span class=\"recipients-inline\"><span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{dielan.id}\" href=\"https://shitposter.club/users/dielan\" rel=\"ugc\">@<span>dielan</span></a></span> <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{coolboymew.id}\" href=\"https://shitposter.club/users/coolboymew\" rel=\"ugc\">@<span>coolboymew</span></a></span> <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{fence.id}\" href=\"https://xyzzy.link/users/fence\" rel=\"ugc\">@<span>fence</span></a></span> <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{hakui.id}\" href=\"https://tuusin.misono-ya.info/users/hakui\" rel=\"ugc\">@<span>hakui</span></a></span> <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{lain.id}\" href=\"https://lain.com/users/lain\" rel=\"ugc\">@<span>lain</span></a></span> </span><p>Haha yeah, you can control who you reply to.</p>"
end
test "the replied-to user is sorted to the left" do
[mario, luigi, wario] = [
insert(:user, nickname: "mario"),
insert(:user, nickname: "luigi"),
insert(:user, nickname: "wario")
]
{:ok, post1} = CommonAPI.post(mario, %{status: "Letsa go!"})
{:ok, post2} =
CommonAPI.post(luigi, %{status: "Oh yaah", in_reply_to_id: post1.id, to: [mario.ap_id]})
activity = %{
"type" => "Create",
"actor" => wario.ap_id,
"object" => %{
"type" => "Note",
"actor" => wario.ap_id,
"content" => "WHA-HA!",
"to" => [
mario.ap_id,
luigi.ap_id,
Constants.as_public()
],
"inReplyTo" => Object.normalize(post2).data["id"]
}
}
{:ok, %{"object" => %{"content" => filtered}}} = ForceMentionsInContent.filter(activity)
assert filtered ==
"<span class=\"recipients-inline\"><span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{luigi.id}\" href=\"#{luigi.ap_id}\" rel=\"ugc\">@<span>luigi</span></a></span> <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{mario.id}\" href=\"#{mario.ap_id}\" rel=\"ugc\">@<span>mario</span></a></span> </span>WHA-HA!"
end end
end end