From f36d14818dadda78c6a5e8cb2977105cb0152bd3 Mon Sep 17 00:00:00 2001 From: floatingghost Date: Wed, 19 Oct 2022 10:01:14 +0000 Subject: [PATCH] Unilateral remove from followers (#232) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit from https://git.pleroma.social/pleroma/pleroma/-/merge_requests/3647/ Co-authored-by: marcin mikołajczak Co-authored-by: Tusooa Zhu Co-authored-by: FloatingGhost Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma/pulls/232 --- CHANGELOG.md | 1 + .../api_spec/operations/account_operation.ex | 16 +++++++ .../controllers/account_controller.ex | 21 +++++++-- lib/pleroma/web/router.ex | 1 + test/pleroma/user_test.exs | 6 +-- .../web/activity_pub/activity_pub_test.exs | 2 +- .../controllers/account_controller_test.exs | 44 +++++++++++++++++++ 7 files changed, 84 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1c2e4460..b3e4f371f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Added - Officially supported docker release +- Ability to remove followers unilaterally without a block ## Changes - Follows no longer override domain blocks, a domain block is final diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index b1f4932ee..b305dc1ea 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -334,6 +334,22 @@ def unblock_operation do } end + def remove_from_followers_operation do + %Operation{ + tags: ["Account actions"], + summary: "Remove from followers", + operationId: "AccountController.remove_from_followers", + security: [%{"oAuth" => ["follow", "write:follows"]}], + description: "Remove the given account from followers", + parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}], + responses: %{ + 200 => Operation.response("Relationship", "application/json", AccountRelationship), + 400 => Operation.response("Error", "application/json", ApiError), + 404 => Operation.response("Error", "application/json", ApiError) + } + } + end + def note_operation do %Operation{ tags: ["Account actions"], diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index ed7fb802a..946c8544f 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -76,15 +76,16 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do plug( OAuthScopesPlug, - %{scopes: ["follow", "write:follows"]} when action in [:follow_by_uri, :follow, :unfollow] + %{scopes: ["follow", "write:follows"]} + when action in [:follow_by_uri, :follow, :unfollow, :remove_from_followers] ) plug(OAuthScopesPlug, %{scopes: ["follow", "read:mutes"]} when action == :mutes) plug(OAuthScopesPlug, %{scopes: ["follow", "write:mutes"]} when action in [:mute, :unmute]) - @relationship_actions [:follow, :unfollow] - @needs_account ~W(followers following lists follow unfollow mute unmute block unblock note)a + @relationship_actions [:follow, :unfollow, :remove_from_followers] + @needs_account ~W(followers following lists follow unfollow mute unmute block unblock note remove_from_followers)a plug( RateLimiter, @@ -447,6 +448,20 @@ def note( end end + @doc "POST /api/v1/accounts/:id/remove_from_followers" + def remove_from_followers(%{assigns: %{user: %{id: id}, account: %{id: id}}}, _params) do + {:error, "Can not unfollow yourself"} + end + + def remove_from_followers(%{assigns: %{user: followed, account: follower}} = conn, _params) do + with {:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do + render(conn, "relationship.json", user: followed, target: follower) + else + nil -> + render_error(conn, :not_found, "Record not found") + end + end + @doc "POST /api/v1/follows" def follow_by_uri(%{body_params: %{uri: uri}} = conn, _) do case User.get_cached_by_nickname(uri) do diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 838599c4d..71a9e4d29 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -509,6 +509,7 @@ defmodule Pleroma.Web.Router do post("/accounts/:id/mute", AccountController, :mute) post("/accounts/:id/unmute", AccountController, :unmute) post("/accounts/:id/note", AccountController, :note) + post("/accounts/:id/remove_from_followers", AccountController, :remove_from_followers) get("/conversations", ConversationController, :index) post("/conversations/:id/read", ConversationController, :mark_as_read) diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs index 0272e3142..f11aec136 100644 --- a/test/pleroma/user_test.exs +++ b/test/pleroma/user_test.exs @@ -311,7 +311,7 @@ test "local users do not automatically follow local locked accounts" do describe "unfollow/2" do setup do: clear_config([:instance, :external_user_synchronization]) - test "unfollow with syncronizes external user" do + test "unfollow with synchronizes external user" do clear_config([:instance, :external_user_synchronization], true) followed = @@ -2260,7 +2260,7 @@ test "updates the counters normally on following/getting a follow when disabled" assert other_user.follower_count == 1 end - test "syncronizes the counters with the remote instance for the followed when enabled" do + test "synchronizes the counters with the remote instance for the followed when enabled" do clear_config([:instance, :external_user_synchronization], false) user = insert(:user) @@ -2282,7 +2282,7 @@ test "syncronizes the counters with the remote instance for the followed when en assert other_user.follower_count == 437 end - test "syncronizes the counters with the remote instance for the follower when enabled" do + test "synchronizes the counters with the remote instance for the follower when enabled" do clear_config([:instance, :external_user_synchronization], false) user = insert(:user) diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs index ec562ac7b..c7b3334f3 100644 --- a/test/pleroma/web/activity_pub/activity_pub_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_test.exs @@ -1632,7 +1632,7 @@ test "fetches only public posts for other users" do end describe "fetch_follow_information_for_user" do - test "syncronizes following/followers counters" do + test "synchronizes following/followers counters" do user = insert(:user, local: false, diff --git a/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs index dcdff6c09..5b4f12402 100644 --- a/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs @@ -1921,4 +1921,48 @@ test "create a note on a user" do |> get("/api/v1/accounts/relationships?id=#{other_user.id}") |> json_response_and_validate_schema(200) end + + describe "remove from followers" do + setup do: oauth_access(["follow"]) + + test "removing user from followers", %{conn: conn, user: user} do + %{id: other_user_id} = other_user = insert(:user) + + CommonAPI.follow(other_user, user) + + assert %{"id" => ^other_user_id, "followed_by" => false} = + conn + |> post("/api/v1/accounts/#{other_user_id}/remove_from_followers") + |> json_response_and_validate_schema(200) + + refute User.following?(other_user, user) + end + + test "removing remote user from followers", %{conn: conn, user: user} do + %{id: other_user_id} = other_user = insert(:user, local: false) + + CommonAPI.follow(other_user, user) + + assert User.following?(other_user, user) + + assert %{"id" => ^other_user_id, "followed_by" => false} = + conn + |> post("/api/v1/accounts/#{other_user_id}/remove_from_followers") + |> json_response_and_validate_schema(200) + + refute User.following?(other_user, user) + end + + test "removing user from followers errors", %{user: user, conn: conn} do + # self remove + conn_res = post(conn, "/api/v1/accounts/#{user.id}/remove_from_followers") + + assert %{"error" => "Can not unfollow yourself"} = + json_response_and_validate_schema(conn_res, 400) + + # remove non existing user + conn_res = post(conn, "/api/v1/accounts/doesntexist/remove_from_followers") + assert %{"error" => "Record not found"} = json_response_and_validate_schema(conn_res, 404) + end + end end