diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 8ab87bfee..03e72be58 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -327,7 +327,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end end - defp do_unfollow(follower, followed, activity_id, local) do + defp do_unfollow(follower, followed, activity_id, local) + + defp do_unfollow(follower, followed, activity_id, local) when local == true do with %Activity{} = follow_activity <- fetch_latest_follow(follower, followed), {:ok, follow_activity} <- update_follow_state(follow_activity, "cancelled"), unfollow_data <- make_unfollow_data(follower, followed, follow_activity, activity_id), @@ -341,6 +343,32 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end end + defp do_unfollow(follower, followed, activity_id, false) do + # On a remote unfollow, _remove_ their activity from the database, since some software (MISSKEEEEY) + # uses deterministic ids for follows. + with %Activity{} = follow_activity <- fetch_latest_follow(follower, followed), + {:ok, _activity} <- Repo.delete(follow_activity), + unfollow_data <- make_unfollow_data(follower, followed, follow_activity, activity_id), + unfollow_activity <- remote_unfollow_data(unfollow_data), + _ <- notify_and_stream(unfollow_activity) do + {:ok, unfollow_activity} + else + nil -> nil + {:error, error} -> Repo.rollback(error) + end + end + + defp remote_unfollow_data(data) do + {recipients, _, _} = get_recipients(data) + + %Activity{ + data: data, + local: false, + actor: data["actor"], + recipients: recipients + } + end + @spec flag(map()) :: {:ok, Activity.t()} | {:error, any()} def flag(params) do with {:ok, result} <- Repo.transaction(fn -> do_flag(params) end) do diff --git a/priv/repo/migrations/20220805123645_remove_remote_cancelled_follow_requests.exs b/priv/repo/migrations/20220805123645_remove_remote_cancelled_follow_requests.exs new file mode 100644 index 000000000..3159843d1 --- /dev/null +++ b/priv/repo/migrations/20220805123645_remove_remote_cancelled_follow_requests.exs @@ -0,0 +1,35 @@ +defmodule Pleroma.Repo.Migrations.RemoveRemoteCancelledFollowRequests do + use Ecto.Migration + + def up do + statement = """ + DELETE FROM + activities + WHERE + (data->>'type') = 'Follow' + AND + (data->>'state') = 'cancelled' + AND + local = false; + """ + + execute(statement) + + statement = """ + DELETE FROM + activities + WHERE + (data->>'type') = 'Undo' + AND + (data->'object'->>'type') = 'Follow' + AND + local = false; + """ + + execute(statement) + end + + def down do + :ok + end +end diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs index e6cc20bba..90680e8cc 100644 --- a/test/pleroma/web/activity_pub/activity_pub_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_test.exs @@ -1375,6 +1375,21 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do assert embedded_object["object"] == followed.ap_id assert embedded_object["id"] == follow_activity.data["id"] end + + test "it removes the follow activity if it was remote" do + follower = insert(:user, local: false) + followed = insert(:user) + + {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed) + {:ok, activity} = ActivityPub.unfollow(follower, followed, nil, false) + + assert activity.data["type"] == "Undo" + assert activity.data["actor"] == follower.ap_id + + activity = Activity.get_by_id(follow_activity.id) + assert is_nil(activity) + assert is_nil(Utils.fetch_latest_follow(follower, followed)) + end end describe "timeline post-processing" do