Skip to content

Commit

Permalink
Add ability to auto-approve followbacks
Browse files Browse the repository at this point in the history
Resolves: https://akkoma.dev/AkkomaGang/akkoma/issues/148
Signed-off-by: marcin mikołajczak <[email protected]>
  • Loading branch information
TheOneric authored and mkljczk committed Aug 29, 2024
1 parent cc6121a commit 23b2e04
Show file tree
Hide file tree
Showing 10 changed files with 81 additions and 14 deletions.
1 change: 1 addition & 0 deletions docs/development/API/differences_in_mastoapi_responses.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
28 changes: 21 additions & 7 deletions lib/pleroma/user.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -588,7 +589,8 @@ defmodule Pleroma.User do
:disclose_client,
:birthday,
:show_birthday,
:location
:location,
:permit_followback
]
)
|> validate_min_age()
Expand Down Expand Up @@ -1143,16 +1145,21 @@ defmodule Pleroma.User do

def needs_update?(_), do: true

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

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
Expand Down Expand Up @@ -1526,6 +1533,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)
Expand Down
2 changes: 1 addition & 1 deletion lib/pleroma/web/activity_pub/side_effects.ex
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,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
Expand Down
9 changes: 8 additions & 1 deletion lib/pleroma/web/api_spec/operations/account_operation.ex
Original file line number Diff line number Diff line change
Expand Up @@ -822,6 +822,12 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation 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,
Expand Down Expand Up @@ -858,7 +864,8 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
discoverable: false,
actor_type: "Person",
show_birthday: false,
birthday: "2001-02-12"
birthday: "2001-02-12",
permit_followback: true
}
}
end
Expand Down
6 changes: 4 additions & 2 deletions lib/pleroma/web/api_spec/schemas/account.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down Expand Up @@ -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" => [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|> 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:
#
Expand Down
3 changes: 2 additions & 1 deletion lib/pleroma/web/mastodon_api/views/account_view.ex
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView 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])
Expand Down
9 changes: 9 additions & 0 deletions priv/repo/migrations/20240213120000_add_permit_followback.exs
Original file line number Diff line number Diff line change
@@ -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
30 changes: 30 additions & 0 deletions test/pleroma/web/common_api_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -1472,6 +1472,36 @@ defmodule Pleroma.Web.CommonAPITest do

assert User.following?(follower, followed)
end

test "directly follows back a locked, but followback-allowing local user" do

Check failure on line 1476 in test/pleroma/web/common_api_test.exs

View workflow job for this annotation

GitHub Actions / Test on OTP 26.2.5 / Elixir 1.15.8

test follow/2 directly follows back a locked, but followback-allowing local user (Pleroma.Web.CommonAPITest)
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

Check failure on line 1491 in test/pleroma/web/common_api_test.exs

View workflow job for this annotation

GitHub Actions / Test on OTP 26.2.5 / Elixir 1.15.8

test follow/2 creates a pending request for locked, non-followback local user (Pleroma.Web.CommonAPITest)
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
Expand Down
6 changes: 4 additions & 2 deletions test/pleroma/web/mastodon_api/views/account_view_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
location: nil,
is_local: true,
avatar_description: "",
header_description: ""
header_description: "",
permit_followback: false
}
}

Expand Down Expand Up @@ -310,7 +311,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
location: nil,
is_local: true,
avatar_description: "",
header_description: ""
header_description: "",
permit_followback: false
}
}

Expand Down

0 comments on commit 23b2e04

Please sign in to comment.