Unilateral remove from followers (#232)

from https://git.pleroma.social/pleroma/pleroma/-/merge_requests/3647/

Co-authored-by: marcin mikołajczak <git@mkljczk.pl>
Co-authored-by: Tusooa Zhu <tusooa@kazv.moe>
Co-authored-by: FloatingGhost <hannah@coffee-and-dreams.uk>
Reviewed-on: AkkomaGang/akkoma#232
This commit is contained in:
floatingghost 2022-10-19 10:01:14 +00:00
parent 5231d436d1
commit f36d14818d
7 changed files with 84 additions and 7 deletions

View file

@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## Added ## Added
- Officially supported docker release - Officially supported docker release
- Ability to remove followers unilaterally without a block
## Changes ## Changes
- Follows no longer override domain blocks, a domain block is final - Follows no longer override domain blocks, a domain block is final

View file

@ -334,6 +334,22 @@ def unblock_operation do
} }
end 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 def note_operation do
%Operation{ %Operation{
tags: ["Account actions"], tags: ["Account actions"],

View file

@ -76,15 +76,16 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
plug( plug(
OAuthScopesPlug, 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", "read:mutes"]} when action == :mutes)
plug(OAuthScopesPlug, %{scopes: ["follow", "write:mutes"]} when action in [:mute, :unmute]) plug(OAuthScopesPlug, %{scopes: ["follow", "write:mutes"]} when action in [:mute, :unmute])
@relationship_actions [:follow, :unfollow] @relationship_actions [:follow, :unfollow, :remove_from_followers]
@needs_account ~W(followers following lists follow unfollow mute unmute block unblock note)a @needs_account ~W(followers following lists follow unfollow mute unmute block unblock note remove_from_followers)a
plug( plug(
RateLimiter, RateLimiter,
@ -447,6 +448,20 @@ def note(
end end
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" @doc "POST /api/v1/follows"
def follow_by_uri(%{body_params: %{uri: uri}} = conn, _) do def follow_by_uri(%{body_params: %{uri: uri}} = conn, _) do
case User.get_cached_by_nickname(uri) do case User.get_cached_by_nickname(uri) do

View file

@ -509,6 +509,7 @@ defmodule Pleroma.Web.Router do
post("/accounts/:id/mute", AccountController, :mute) post("/accounts/:id/mute", AccountController, :mute)
post("/accounts/:id/unmute", AccountController, :unmute) post("/accounts/:id/unmute", AccountController, :unmute)
post("/accounts/:id/note", AccountController, :note) post("/accounts/:id/note", AccountController, :note)
post("/accounts/:id/remove_from_followers", AccountController, :remove_from_followers)
get("/conversations", ConversationController, :index) get("/conversations", ConversationController, :index)
post("/conversations/:id/read", ConversationController, :mark_as_read) post("/conversations/:id/read", ConversationController, :mark_as_read)

View file

@ -311,7 +311,7 @@ test "local users do not automatically follow local locked accounts" do
describe "unfollow/2" do describe "unfollow/2" do
setup do: clear_config([:instance, :external_user_synchronization]) 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) clear_config([:instance, :external_user_synchronization], true)
followed = followed =
@ -2260,7 +2260,7 @@ test "updates the counters normally on following/getting a follow when disabled"
assert other_user.follower_count == 1 assert other_user.follower_count == 1
end 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) clear_config([:instance, :external_user_synchronization], false)
user = insert(:user) 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 assert other_user.follower_count == 437
end 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) clear_config([:instance, :external_user_synchronization], false)
user = insert(:user) user = insert(:user)

View file

@ -1632,7 +1632,7 @@ test "fetches only public posts for other users" do
end end
describe "fetch_follow_information_for_user" do describe "fetch_follow_information_for_user" do
test "syncronizes following/followers counters" do test "synchronizes following/followers counters" do
user = user =
insert(:user, insert(:user,
local: false, local: false,

View file

@ -1921,4 +1921,48 @@ test "create a note on a user" do
|> get("/api/v1/accounts/relationships?id=#{other_user.id}") |> get("/api/v1/accounts/relationships?id=#{other_user.id}")
|> json_response_and_validate_schema(200) |> json_response_and_validate_schema(200)
end 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 end