commit: a0af6cba09f6c85f524d31f7d1a68948b77d9e53
parent 540e62c5fcab3887d7ca488cb3f464f000f69523
Author: Mark Felder <feld@feld.me>
Date: Thu, 8 Aug 2024 14:12:44 -0400
Added MRF.QuietReply which prevents replies to public posts from being published to the timelines
Diffstat:
4 files changed, 169 insertions(+), 6 deletions(-)
diff --git a/changelog.d/mrf-quietreply.add b/changelog.d/mrf-quietreply.add
@@ -0,0 +1 @@
+Added MRF.QuietReply which prevents replies to public posts from being published to the timelines
diff --git a/lib/pleroma/web/activity_pub/mrf/quiet_reply.ex b/lib/pleroma/web/activity_pub/mrf/quiet_reply.ex
@@ -0,0 +1,55 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2023 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.QuietReply do
+ require Pleroma.Constants
+
+ alias Pleroma.User
+
+ @behaviour Pleroma.Web.ActivityPub.MRF.Policy
+
+ @impl true
+ def history_awareness, do: :auto
+
+ @impl true
+ def filter(
+ %{
+ "type" => "Create",
+ "object" => %{
+ "actor" => actor,
+ "type" => "Note",
+ "to" => to,
+ "cc" => cc,
+ "inReplyTo" => in_reply_to
+ }
+ } = object
+ ) do
+ with true <- is_binary(in_reply_to),
+ false <- match?([], cc),
+ %User{follower_address: followers_collection, local: true} <-
+ User.get_by_ap_id(actor) do
+ updated_to =
+ to
+ |> Kernel.++([followers_collection])
+ |> Kernel.--([Pleroma.Constants.as_public()])
+
+ updated_cc = [Pleroma.Constants.as_public()]
+
+ updated_object =
+ object
+ |> put_in(["object", "to"], updated_to)
+ |> put_in(["object", "cc"], updated_cc)
+
+ {:ok, updated_object}
+ else
+ _ -> {:ok, object}
+ end
+ 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/quiet_reply_test.exs b/test/pleroma/web/activity_pub/mrf/quiet_reply_test.exs
@@ -0,0 +1,103 @@
+# 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.QuietReplyTest do
+ use Pleroma.DataCase
+ import Pleroma.Factory
+
+ require Pleroma.Constants
+
+ alias Pleroma.Object
+ alias Pleroma.Web.ActivityPub.MRF.QuietReply
+ alias Pleroma.Web.CommonAPI
+
+ test "replying to public post is forced to be quiet" do
+ batman = insert(:user, nickname: "batman")
+ robin = insert(:user, nickname: "robin")
+
+ {:ok, post} = CommonAPI.post(batman, %{status: "To the Batmobile!"})
+
+ reply = %{
+ "type" => "Create",
+ "actor" => robin.ap_id,
+ "object" => %{
+ "type" => "Note",
+ "actor" => robin.ap_id,
+ "content" => "@batman Wait up, I forgot my spandex!",
+ "to" => [
+ batman.ap_id,
+ Pleroma.Constants.as_public()
+ ],
+ "cc" => [robin.follower_address],
+ "inReplyTo" => Object.normalize(post).data["id"]
+ }
+ }
+
+ expected_to = [batman.ap_id, robin.follower_address]
+ expected_cc = [Pleroma.Constants.as_public()]
+
+ assert {:ok, filtered} = QuietReply.filter(reply)
+
+ assert expected_to == filtered["object"]["to"]
+ assert expected_cc == filtered["object"]["cc"]
+ end
+
+ test "replying direct is unmodified" do
+ batman = insert(:user, nickname: "batman")
+ robin = insert(:user, nickname: "robin")
+
+ {:ok, post} = CommonAPI.post(batman, %{status: "To the Batmobile!"})
+
+ reply = %{
+ "type" => "Create",
+ "actor" => robin.ap_id,
+ "object" => %{
+ "type" => "Note",
+ "actor" => robin.ap_id,
+ "content" => "@batman Wait up, I forgot my spandex!",
+ "to" => [batman.ap_id],
+ "cc" => [],
+ "inReplyTo" => Object.normalize(post).data["id"]
+ }
+ }
+
+ assert {:ok, filtered} = QuietReply.filter(reply)
+
+ assert match?(^filtered, reply)
+ end
+
+ test "replying followers-only is unmodified" do
+ batman = insert(:user, nickname: "batman")
+ robin = insert(:user, nickname: "robin")
+
+ {:ok, post} = CommonAPI.post(batman, %{status: "To the Batmobile!"})
+
+ reply = %{
+ "type" => "Create",
+ "actor" => robin.ap_id,
+ "object" => %{
+ "type" => "Note",
+ "actor" => robin.ap_id,
+ "content" => "@batman Wait up, I forgot my spandex!",
+ "to" => [batman.ap_id, robin.follower_address],
+ "cc" => [],
+ "inReplyTo" => Object.normalize(post).data["id"]
+ }
+ }
+
+ assert {:ok, filtered} = QuietReply.filter(reply)
+
+ assert match?(^filtered, reply)
+ end
+
+ test "non-reply posts are unmodified" do
+ batman = insert(:user, nickname: "batman")
+
+ {:ok, post} = CommonAPI.post(batman, %{status: "To the Batmobile!"})
+
+ assert {:ok, filtered} = QuietReply.filter(post)
+
+ assert match?(^filtered, post)
+ end
+end
diff --git a/test/support/factory.ex b/test/support/factory.ex
@@ -53,6 +53,15 @@ defmodule Pleroma.Factory do
keys: pem
}
+ attrs = Map.delete(attrs, :domain)
+
+ user
+ |> Map.put(:raw_bio, user.bio)
+ |> merge_attributes(attrs)
+ |> user_urls(attrs)
+ end
+
+ defp user_urls(user, attrs) do
urls =
if attrs[:local] == false do
base_domain = attrs[:domain] || Enum.random(["domain1.com", "domain2.com", "domain3.com"])
@@ -75,12 +84,7 @@ defmodule Pleroma.Factory do
}
end
- attrs = Map.delete(attrs, :domain)
-
- user
- |> Map.put(:raw_bio, user.bio)
- |> Map.merge(urls)
- |> merge_attributes(attrs)
+ Map.merge(user, urls)
end
def user_relationship_factory(attrs \\ %{}) do