Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
11 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex b/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex
index 715771d9d..255910b2f 100644
--- a/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex
+++ b/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex
@@ -1,119 +1,132 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent do
require Pleroma.Constants
alias Pleroma.Formatter
alias Pleroma.Object
alias Pleroma.User
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
defp do_extract({:a, attrs, _}, acc) do
if Enum.find(attrs, fn {name, value} ->
name == "class" && value in ["mention", "u-url mention", "mention u-url"]
end) do
href = Enum.find(attrs, fn {name, _} -> name == "href" end) |> elem(1)
acc ++ [href]
else
acc
end
end
defp do_extract({_, _, children}, acc) do
do_extract(children, acc)
end
defp do_extract(nodes, acc) when is_list(nodes) do
Enum.reduce(nodes, acc, fn node, acc -> do_extract(node, acc) end)
end
defp do_extract(_, acc), do: acc
defp extract_mention_uris_from_content(content) do
{:ok, tree} = :fast_html.decode(content, format: [:html_atoms])
do_extract(tree, [])
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
# Drop constants and the actor's own AP ID
defp clean_recipients(recipients, object) do
Enum.reject(recipients, fn ap_id ->
ap_id in [
object["object"]["actor"],
Pleroma.Constants.as_public(),
Pleroma.Web.ActivityPub.Utils.as_local_public()
]
end)
end
@impl true
def filter(
%{
"type" => "Create",
"object" => %{"type" => "Note", "to" => to, "inReplyTo" => in_reply_to}
} = object
)
when is_list(to) and is_binary(in_reply_to) do
# image-only posts from pleroma apparently reach this MRF without the content field
content = object["object"]["content"] || ""
# Get the replied-to user for sorting
replied_to_user = get_replied_to_user(object["object"])
mention_users =
to
|> clean_recipients(object)
|> Enum.map(&User.get_cached_by_ap_id/1)
|> Enum.reject(&is_nil/1)
|> sort_replied_user(replied_to_user)
explicitly_mentioned_uris = extract_mention_uris_from_content(content)
added_mentions =
Enum.reduce(mention_users, "", fn %User{ap_id: uri} = user, acc ->
unless uri in explicitly_mentioned_uris do
acc <> Formatter.mention_from_user(user, %{mentions_format: :compact}) <> " "
else
acc
end
end)
- content =
+ recipients_inline =
if added_mentions != "",
- do: "<span class=\"recipients-inline\">#{added_mentions}</span>" <> content,
- else: content
+ do: "<span class=\"recipients-inline\">#{added_mentions}</span>",
+ else: ""
+
+ content =
+ cond do
+ # For Markdown posts, insert the mentions inside the first <p> tag
+ recipients_inline != "" && String.starts_with?(content, "<p>") ->
+ "<p>" <> recipients_inline <> String.trim_leading(content, "<p>")
+
+ recipients_inline != "" ->
+ recipients_inline <> content
+
+ true ->
+ content
+ end
{:ok, put_in(object["object"]["content"], content)}
end
@impl true
def filter(object), do: {:ok, object}
@impl true
def describe, do: {:ok, %{}}
end
diff --git a/test/pleroma/web/activity_pub/mrf/force_mentions_in_content_test.exs b/test/pleroma/web/activity_pub/mrf/force_mentions_in_content_test.exs
index 6bcf75a92..669ec5251 100644
--- a/test/pleroma/web/activity_pub/mrf/force_mentions_in_content_test.exs
+++ b/test/pleroma/web/activity_pub/mrf/force_mentions_in_content_test.exs
@@ -1,137 +1,164 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.ForceMentionsInContentTest do
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
[lain, coolboymew, dielan, hakui, fence] = [
insert(:user, ap_id: "https://lain.com/users/lain", nickname: "lain@lain.com", local: false),
insert(:user,
ap_id: "https://shitposter.club/users/coolboymew",
nickname: "coolboymew@shitposter.club",
local: false
),
insert(:user,
ap_id: "https://shitposter.club/users/dielan",
nickname: "dielan@shitposter.club",
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!()
activity = %{
"type" => "Create",
"actor" => "https://gleasonator.com/users/alex",
"object" => object
}
{:ok, %{"object" => %{"content" => filtered}}} = ForceMentionsInContent.filter(activity)
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>"
+ "<p><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>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
test "don't mention self" do
mario = insert(:user, nickname: "mario")
{:ok, post} = CommonAPI.post(mario, %{status: "Mama mia"})
activity = %{
"type" => "Create",
"actor" => mario.ap_id,
"object" => %{
"type" => "Note",
"actor" => mario.ap_id,
"content" => "I'ma tired...",
"to" => [
mario.ap_id,
Constants.as_public()
],
"inReplyTo" => Object.normalize(post).data["id"]
}
}
{:ok, %{"object" => %{"content" => filtered}}} = ForceMentionsInContent.filter(activity)
assert filtered == "I'ma tired..."
end
test "don't mention in top-level posts" do
mario = insert(:user, nickname: "mario")
luigi = insert(:user, nickname: "luigi")
{:ok, post} = CommonAPI.post(mario, %{status: "Letsa go"})
activity = %{
"type" => "Create",
"actor" => mario.ap_id,
"object" => %{
"type" => "Note",
"actor" => mario.ap_id,
"content" => "Mama mia!",
"to" => [
luigi.ap_id,
Constants.as_public()
],
"quoteUrl" => Object.normalize(post).data["id"]
}
}
{:ok, %{"object" => %{"content" => filtered}}} = ForceMentionsInContent.filter(activity)
assert filtered == "Mama mia!"
end
+
+ test "with markdown formatting" do
+ mario = insert(:user, nickname: "mario")
+ luigi = insert(:user, nickname: "luigi")
+
+ {:ok, post} = CommonAPI.post(luigi, %{status: "Mama mia"})
+
+ activity = %{
+ "type" => "Create",
+ "actor" => mario.ap_id,
+ "object" => %{
+ "type" => "Note",
+ "actor" => mario.ap_id,
+ "content" => "<p>I'ma tired...</p>",
+ "to" => [
+ luigi.ap_id,
+ Constants.as_public()
+ ],
+ "inReplyTo" => Object.normalize(post).data["id"]
+ }
+ }
+
+ {:ok, %{"object" => %{"content" => filtered}}} = ForceMentionsInContent.filter(activity)
+
+ assert filtered ==
+ "<p><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>I'ma tired...</p>"
+ end
end

File Metadata

Mime Type
text/x-diff
Expires
Sat, Oct 31, 2:15 AM (16 h, 40 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1800898
Default Alt Text
(11 KB)

Event Timeline