Add ability to auto-approve followbacks

Resolves: https://akkoma.dev/AkkomaGang/akkoma/issues/148
This commit is contained in:
Oneric 2024-01-17 19:13:29 +00:00
parent 13e62b4e51
commit 376f6b15ca
11 changed files with 80 additions and 12 deletions

View file

@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## Added
- Full compatibility with Erlang OTP26
- handling of GET /api/v1/preferences
- ability to auto-approve follow requests from users you are already following
## Changed
- OTP builds are now built on erlang OTP26

View file

@ -117,6 +117,7 @@ Has these additional fields under the `akkoma` object:
- `instance`: nullable object with metadata about the users instance
- `status_ttl_days`: nullable int, default time after which statuses are deleted
- `permit_followback`: boolean, whether follows from followed accounts are auto-approved
### Source

View file

@ -160,6 +160,7 @@ defmodule Pleroma.User do
field(:last_status_at, :naive_datetime)
field(:language, :string)
field(:status_ttl_days, :integer, default: nil)
field(:permit_followback, :boolean, default: false)
field(:accepts_direct_messages_from, Ecto.Enum,
values: [:everybody, :people_i_follow, :nobody],
@ -544,6 +545,7 @@ defmodule Pleroma.User do
:actor_type,
:disclose_client,
:status_ttl_days,
:permit_followback,
:accepts_direct_messages_from
]
)
@ -972,16 +974,21 @@ defmodule Pleroma.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
def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
if can_direct_follow_local(follower, followed) do
follow(follower, followed)
else
follow(follower, followed, :follow_pending)
end
def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
follow(follower, followed)
end
def maybe_direct_follow(%User{} = follower, %User{} = followed) do
@ -1331,6 +1338,13 @@ defmodule Pleroma.User do
|> 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

@ -109,7 +109,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
%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

@ -723,6 +723,12 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
description:
"Number of days after which statuses will be deleted. Set to -1 to disable."
},
permit_followback: %Schema{
allOf: [BooleanLike],
nullable: true,
description:
"Whether follow requests from accounts the user is already following are auto-approved (when locked)."
},
accepts_direct_messages_from: %Schema{
type: :string,
enum: [
@ -754,6 +760,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
discoverable: false,
actor_type: "Person",
status_ttl_days: 30,
permit_followback: true,
accepts_direct_messages_from: "everybody"
}
}

View file

@ -122,7 +122,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
nodeinfo: %Schema{type: :object, nullable: true}
}
},
status_ttl_days: %Schema{type: :integer, nullable: true}
status_ttl_days: %Schema{type: :integer, nullable: true},
permit_followback: %Schema{type: :boolean}
}
},
source: %Schema{
@ -224,7 +225,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
# XXX: nodeinfo schema
}
},
"status_ttl_days" => nil
"status_ttl_days" => nil,
"permit_followback" => true
},
"source" => %{
"fields" => [],

View file

@ -222,6 +222,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|> Maps.put_if_present(:language, Pleroma.Web.Gettext.normalize_locale(params[:language]))
|> Maps.put_if_present(:status_ttl_days, params[:status_ttl_days], status_ttl_days_value)
|> Maps.put_if_present(:accepts_direct_messages_from, params[:accepts_direct_messages_from])
|> Maps.put_if_present(:permit_followback, params[:permit_followback])
# What happens here:
#

View file

@ -292,7 +292,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
last_status_at: user.last_status_at,
akkoma: %{
instance: render("instance.json", %{instance: instance}),
status_ttl_days: user.status_ttl_days
status_ttl_days: user.status_ttl_days,
permit_followback: user.permit_followback
},
# Pleroma extensions
# Note: it's insecure to output :email but fully-qualified nickname may serve as safe stub

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

@ -1061,6 +1061,36 @@ defmodule Pleroma.Web.CommonAPITest 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

@ -65,7 +65,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
},
favicon: nil
},
status_ttl_days: 5
status_ttl_days: 5,
permit_followback: false
},
avatar: "http://localhost:4001/images/avi.png",
avatar_static: "http://localhost:4001/images/avi.png",
@ -248,7 +249,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
favicon: "http://localhost:4001/favicon.png",
nodeinfo: %{version: "2.0"}
},
status_ttl_days: nil
status_ttl_days: nil,
permit_followback: false
},
pleroma: %{
ap_id: user.ap_id,