From 23b2e044a56990b0d15c3e7d379be7589534ab29 Mon Sep 17 00:00:00 2001 From: Oneric Date: Wed, 17 Jan 2024 19:13:29 +0000 Subject: [PATCH] Add ability to auto-approve followbacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolves: https://akkoma.dev/AkkomaGang/akkoma/issues/148 Signed-off-by: marcin mikołajczak --- .../API/differences_in_mastoapi_responses.md | 1 + lib/pleroma/user.ex | 28 ++++++++++++----- lib/pleroma/web/activity_pub/side_effects.ex | 2 +- .../api_spec/operations/account_operation.ex | 9 +++++- lib/pleroma/web/api_spec/schemas/account.ex | 6 ++-- .../controllers/account_controller.ex | 1 + .../web/mastodon_api/views/account_view.ex | 3 +- .../20240213120000_add_permit_followback.exs | 9 ++++++ test/pleroma/web/common_api_test.exs | 30 +++++++++++++++++++ .../mastodon_api/views/account_view_test.exs | 6 ++-- 10 files changed, 81 insertions(+), 14 deletions(-) create mode 100644 priv/repo/migrations/20240213120000_add_permit_followback.exs diff --git a/docs/development/API/differences_in_mastoapi_responses.md b/docs/development/API/differences_in_mastoapi_responses.md index d9bfeaabf0..865322d306 100644 --- a/docs/development/API/differences_in_mastoapi_responses.md +++ b/docs/development/API/differences_in_mastoapi_responses.md @@ -149,6 +149,7 @@ Has these additional fields under the `pleroma` object: - `favicon`: nullable URL string, Favicon image of the user's instance - `avatar_description`: string, image description for user avatar, defaults to empty string - `header_description`: string, image description for user banner, defaults to empty string +- `permit_followback`: boolean, whether follows from followed accounts are auto-approved ### Source diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index e98cf71a06..2878b3ecb7 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -164,6 +164,7 @@ defmodule Pleroma.User do field(:location, :string) field(:language, :string) field(:last_move_at, :naive_datetime) + field(:permit_followback, :boolean, default: false) belongs_to(:domain, Domain) @@ -588,7 +589,8 @@ def update_changeset(struct, params \\ %{}) do :disclose_client, :birthday, :show_birthday, - :location + :location, + :permit_followback ] ) |> validate_min_age() @@ -1143,16 +1145,21 @@ def needs_update?(%User{local: false} = user) do def needs_update?(_), do: true + # "Locked" (self-locked) users demand explicit authorization of follow requests + @spec can_direct_follow_local(User.t(), User.t()) :: true | false + def can_direct_follow_local(%User{} = follower, %User{local: true} = followed) do + !followed.is_locked || (followed.permit_followback and is_friend_of(follower, followed)) + end + @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t(), User.t()} | {:error, String.t()} - # "Locked" (self-locked) users demand explicit authorization of follow requests - def maybe_direct_follow(%User{} = follower, %User{local: true, is_locked: true} = followed) do - follow(follower, followed, :follow_pending) - end - def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do - follow(follower, followed) + if can_direct_follow_local(follower, followed) do + follow(follower, followed) + else + follow(follower, followed, :follow_pending) + end end def maybe_direct_follow(%User{} = follower, %User{} = followed) do @@ -1526,6 +1533,13 @@ def get_familiar_followers(%User{} = user, %User{} = current_user, page \\ nil) |> Repo.all() end + def is_friend_of(%User{} = potential_friend, %User{local: true} = user) do + user + |> get_friends_query() + |> where(id: ^potential_friend.id) + |> Repo.exists?() + end + def increase_note_count(%User{} = user) do User |> where(id: ^user.id) diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index e77d0cc31a..6e41798321 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -101,7 +101,7 @@ def handle( %User{} = followed <- User.get_cached_by_ap_id(followed_user), {_, {:ok, _, _}, _, _} <- {:following, User.follow(follower, followed, :follow_pending), follower, followed} do - if followed.local && !followed.is_locked do + if followed.local && User.can_direct_follow_local(follower, followed) do {:ok, accept_data, _} = Builder.accept(followed, object) {:ok, _activity, _} = Pipeline.common_pipeline(accept_data, local: true) end diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index 697be3e2a5..ff4cc1a6dc 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -822,6 +822,12 @@ defp update_credentials_request do nullable: true, description: "User's birthday will be visible" }, + permit_followback: %Schema{ + allOf: [BooleanLike], + nullable: true, + description: + "Whether follow requests from accounts the user is already following are auto-approved (when locked)." + }, location: %Schema{ type: :string, nullable: true, @@ -858,7 +864,8 @@ defp update_credentials_request do discoverable: false, actor_type: "Person", show_birthday: false, - birthday: "2001-02-12" + birthday: "2001-02-12", + permit_followback: true } } end diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex index 90de8da05c..10a0a2b361 100644 --- a/lib/pleroma/web/api_spec/schemas/account.ex +++ b/lib/pleroma/web/api_spec/schemas/account.ex @@ -113,7 +113,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do description: "Favicon image of the user's instance" }, avatar_description: %Schema{type: :string}, - header_description: %Schema{type: :string} + header_description: %Schema{type: :string}, + permit_followback: %Schema{type: :boolean} } }, source: %Schema{ @@ -208,7 +209,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do "settings_store" => %{ "pleroma-fe" => %{} }, - "birthday" => "2001-02-12" + "birthday" => "2001-02-12", + "permit_followback" => true }, "source" => %{ "fields" => [], diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index ff039817b3..97890619e6 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -232,6 +232,7 @@ def update_credentials( |> Maps.put_if_present(:language, Pleroma.Web.Gettext.normalize_locale(params[:language])) |> Maps.put_if_present(:avatar_description, params[:avatar_description]) |> Maps.put_if_present(:header_description, params[:header_description]) + |> Maps.put_if_present(:permit_followback, params[:permit_followback]) # What happens here: # diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index d37194b666..b9263031ee 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -328,7 +328,8 @@ defp do_render("show.json", %{user: user} = opts) do location: user.location, is_local: user.local, avatar_description: avatar_description, - header_description: header_description + header_description: header_description, + permit_followback: user.permit_followback } } |> maybe_put_role(user, opts[:for]) diff --git a/priv/repo/migrations/20240213120000_add_permit_followback.exs b/priv/repo/migrations/20240213120000_add_permit_followback.exs new file mode 100644 index 0000000000..72475a58e5 --- /dev/null +++ b/priv/repo/migrations/20240213120000_add_permit_followback.exs @@ -0,0 +1,9 @@ +defmodule Pleroma.Repo.Migrations.AddPermitFollowback do + use Ecto.Migration + + def change do + alter table(:users) do + add(:permit_followback, :boolean, null: false, default: false) + end + end +end diff --git a/test/pleroma/web/common_api_test.exs b/test/pleroma/web/common_api_test.exs index 5dd5beabe3..2a98573a18 100644 --- a/test/pleroma/web/common_api_test.exs +++ b/test/pleroma/web/common_api_test.exs @@ -1472,6 +1472,36 @@ test "directly follows a non-locked local user" do assert User.following?(follower, followed) end + + test "directly follows back a locked, but followback-allowing local user" do + uopen = insert(:user, is_locked: false) + uselective = insert(:user, is_locked: true, permit_followback: true) + + assert {:ok, uselective, uopen, %{data: %{"state" => "accept"}}} = + CommonAPI.follow(uselective, uopen) + + assert User.get_follow_state(uselective, uopen) == :follow_accept + + assert {:ok, uopen, uselective, %{data: %{"state" => "accept"}}} = + CommonAPI.follow(uopen, uselective) + + assert User.get_follow_state(uopen, uselective) == :follow_accept + end + + test "creates a pending request for locked, non-followback local user" do + uopen = insert(:user, is_locked: false) + ulocked = insert(:user, is_locked: true, permit_followback: false) + + assert {:ok, ulocked, uopen, %{data: %{"state" => "accept"}}} = + CommonAPI.follow(ulocked, uopen) + + assert User.get_follow_state(ulocked, uopen) == :follow_accept + + assert {:ok, uopen, ulocked, %{data: %{"state" => "pending"}}} = + CommonAPI.follow(uopen, ulocked) + + assert User.get_follow_state(uopen, ulocked) == :follow_pending + end end describe "unfollow/2" do diff --git a/test/pleroma/web/mastodon_api/views/account_view_test.exs b/test/pleroma/web/mastodon_api/views/account_view_test.exs index 021c2a930b..6df9bf8422 100644 --- a/test/pleroma/web/mastodon_api/views/account_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/account_view_test.exs @@ -99,7 +99,8 @@ test "Represent a user account" do location: nil, is_local: true, avatar_description: "", - header_description: "" + header_description: "", + permit_followback: false } } @@ -310,7 +311,8 @@ test "Represent a Service(bot) account" do location: nil, is_local: true, avatar_description: "", - header_description: "" + header_description: "", + permit_followback: false } }