logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma git clone https://hacktivis.me/git/pleroma.git
commit: 4e7928c9850251f6d92409d5c6e97c7438499798
parent fe2ed3fbc1f6e83947f4261aa639d944290e17a5
Author: feld <feld@feld.me>
Date:   Mon, 12 Aug 2024 21:46:45 +0000

Merge branch 'followers-only-reply-direct-mrf' into 'develop'

MRF.FODirectReply

See merge request pleroma/pleroma!4091

Diffstat:

Achangelog.d/mrf-fodirectreply.add1+
Alib/pleroma/web/activity_pub/mrf/fo_direct_reply.ex65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/pleroma/web/activity_pub/mrf/fo_direct_reply_test.exs87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 153 insertions(+), 0 deletions(-)

diff --git a/changelog.d/mrf-fodirectreply.add b/changelog.d/mrf-fodirectreply.add @@ -0,0 +1 @@ +Added MRF.FODirectReply which changes replies to followers-only posts to be direct diff --git a/lib/pleroma/web/activity_pub/mrf/fo_direct_reply.ex b/lib/pleroma/web/activity_pub/mrf/fo_direct_reply.ex @@ -0,0 +1,65 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.FODirectReply do + @moduledoc """ + FODirectReply alters the scope of replies to activities which are Followers Only to be Direct. The purpose of this policy is to prevent broken threads for followers of the reply author because their response was to a user that they are not also following. + """ + + alias Pleroma.User + + @behaviour Pleroma.Web.ActivityPub.MRF.Policy + + @impl true + def filter( + %{ + "type" => "Create", + "to" => to, + "object" => %{ + "actor" => actor, + "type" => "Note", + "inReplyTo" => in_reply_to + } + } = activity + ) do + with true <- is_binary(in_reply_to), + %User{follower_address: followers_collection, local: true} <- User.get_by_ap_id(actor), + true <- followers_only?(in_reply_to) do + direct_to = to -- [followers_collection] + + updated_activity = + activity + |> Map.put("cc", []) + |> Map.put("to", direct_to) + |> Map.put("directMessage", true) + |> put_in(["object", "cc"], []) + |> put_in(["object", "to"], direct_to) + + {:ok, updated_activity} + else + _ -> {:ok, activity} + end + end + + @impl true + def filter(activity), do: {:ok, activity} + + @impl true + def describe, do: {:ok, %{}} + + defp followers_only?(parent_ap_id) do + with %Pleroma.Object{} = object <- Pleroma.Object.get_by_ap_id(parent_ap_id), + object_data <- Map.get(object, :data), + %Pleroma.User{} = user <- User.get_cached_by_ap_id(object_data["actor"]) do + if user.follower_address in object_data["to"] do + true + else + false + end + else + _ -> + false + end + end +end diff --git a/test/pleroma/web/activity_pub/mrf/fo_direct_reply_test.exs b/test/pleroma/web/activity_pub/mrf/fo_direct_reply_test.exs @@ -0,0 +1,87 @@ +# 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.FODirectReplyTest do + use Pleroma.DataCase + import Pleroma.Factory + + require Pleroma.Constants + + alias Pleroma.Object + alias Pleroma.Web.ActivityPub.MRF.FODirectReply + alias Pleroma.Web.CommonAPI + + test "replying to followers-only/private is changed to direct" do + batman = insert(:user, nickname: "batman") + robin = insert(:user, nickname: "robin") + + {:ok, post} = + CommonAPI.post(batman, %{ + status: "Has anyone seen Selina Kyle's latest selfies?", + visibility: "private" + }) + + reply = %{ + "type" => "Create", + "actor" => robin.ap_id, + "to" => [batman.ap_id, robin.follower_address], + "cc" => [], + "object" => %{ + "type" => "Note", + "actor" => robin.ap_id, + "content" => "@batman 🤤 ❤️ 🐈‍⬛", + "to" => [batman.ap_id, robin.follower_address], + "cc" => [], + "inReplyTo" => Object.normalize(post).data["id"] + } + } + + expected_to = [batman.ap_id] + expected_cc = [] + + assert {:ok, filtered} = FODirectReply.filter(reply) + + assert expected_to == filtered["to"] + assert expected_cc == filtered["cc"] + assert expected_to == filtered["object"]["to"] + assert expected_cc == filtered["object"]["cc"] + end + + test "replies to public posts are unmodified" do + batman = insert(:user, nickname: "batman") + robin = insert(:user, nickname: "robin") + + {:ok, post} = + CommonAPI.post(batman, %{status: "Has anyone seen Selina Kyle's latest selfies?"}) + + reply = %{ + "type" => "Create", + "actor" => robin.ap_id, + "to" => [batman.ap_id, robin.follower_address], + "cc" => [], + "object" => %{ + "type" => "Note", + "actor" => robin.ap_id, + "content" => "@batman 🤤 ❤️ 🐈<200d>⬛", + "to" => [batman.ap_id, robin.follower_address], + "cc" => [], + "inReplyTo" => Object.normalize(post).data["id"] + } + } + + assert {:ok, filtered} = FODirectReply.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} = FODirectReply.filter(post) + + assert match?(^filtered, post) + end +end