Add ability to auto-approve followbacks

Resolves: https://akkoma.dev/AkkomaGang/akkoma/issues/148
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
Oneric 2024-01-17 19:13:29 +00:00 committed by marcin mikołajczak
parent cc6121a948
commit 23b2e044a5
10 changed files with 81 additions and 14 deletions

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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" => [],

View file

@ -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:
#

View file

@ -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])

View file

@ -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

View file

@ -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

View file

@ -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
}
}