forked from AkkomaGang/akkoma
Add ability to auto-approve followbacks
Resolves: AkkomaGang/akkoma#148
This commit is contained in:
parent
13e62b4e51
commit
376f6b15ca
11 changed files with 80 additions and 12 deletions
|
@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
## Added
|
## Added
|
||||||
- Full compatibility with Erlang OTP26
|
- Full compatibility with Erlang OTP26
|
||||||
- handling of GET /api/v1/preferences
|
- handling of GET /api/v1/preferences
|
||||||
|
- ability to auto-approve follow requests from users you are already following
|
||||||
|
|
||||||
## Changed
|
## Changed
|
||||||
- OTP builds are now built on erlang OTP26
|
- OTP builds are now built on erlang OTP26
|
||||||
|
|
|
@ -117,6 +117,7 @@ Has these additional fields under the `akkoma` object:
|
||||||
|
|
||||||
- `instance`: nullable object with metadata about the user’s instance
|
- `instance`: nullable object with metadata about the user’s instance
|
||||||
- `status_ttl_days`: nullable int, default time after which statuses are deleted
|
- `status_ttl_days`: nullable int, default time after which statuses are deleted
|
||||||
|
- `permit_followback`: boolean, whether follows from followed accounts are auto-approved
|
||||||
|
|
||||||
### Source
|
### Source
|
||||||
|
|
||||||
|
|
|
@ -160,6 +160,7 @@ defmodule Pleroma.User do
|
||||||
field(:last_status_at, :naive_datetime)
|
field(:last_status_at, :naive_datetime)
|
||||||
field(:language, :string)
|
field(:language, :string)
|
||||||
field(:status_ttl_days, :integer, default: nil)
|
field(:status_ttl_days, :integer, default: nil)
|
||||||
|
field(:permit_followback, :boolean, default: false)
|
||||||
|
|
||||||
field(:accepts_direct_messages_from, Ecto.Enum,
|
field(:accepts_direct_messages_from, Ecto.Enum,
|
||||||
values: [:everybody, :people_i_follow, :nobody],
|
values: [:everybody, :people_i_follow, :nobody],
|
||||||
|
@ -544,6 +545,7 @@ def update_changeset(struct, params \\ %{}) do
|
||||||
:actor_type,
|
:actor_type,
|
||||||
:disclose_client,
|
:disclose_client,
|
||||||
:status_ttl_days,
|
:status_ttl_days,
|
||||||
|
:permit_followback,
|
||||||
:accepts_direct_messages_from
|
:accepts_direct_messages_from
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@ -972,16 +974,21 @@ def needs_update?(%User{local: false} = user) do
|
||||||
|
|
||||||
def needs_update?(_), do: true
|
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()) ::
|
@spec maybe_direct_follow(User.t(), User.t()) ::
|
||||||
{:ok, User.t(), User.t()} | {:error, String.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
|
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
|
end
|
||||||
|
|
||||||
def maybe_direct_follow(%User{} = follower, %User{} = followed) do
|
def maybe_direct_follow(%User{} = follower, %User{} = followed) do
|
||||||
|
@ -1331,6 +1338,13 @@ def get_friends_ids(%User{} = user, page \\ nil) do
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
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
|
def increase_note_count(%User{} = user) do
|
||||||
User
|
User
|
||||||
|> where(id: ^user.id)
|
|> where(id: ^user.id)
|
||||||
|
|
|
@ -109,7 +109,7 @@ def handle(
|
||||||
%User{} = followed <- User.get_cached_by_ap_id(followed_user),
|
%User{} = followed <- User.get_cached_by_ap_id(followed_user),
|
||||||
{_, {:ok, _, _}, _, _} <-
|
{_, {:ok, _, _}, _, _} <-
|
||||||
{:following, User.follow(follower, followed, :follow_pending), follower, followed} do
|
{: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, accept_data, _} = Builder.accept(followed, object)
|
||||||
{:ok, _activity, _} = Pipeline.common_pipeline(accept_data, local: true)
|
{:ok, _activity, _} = Pipeline.common_pipeline(accept_data, local: true)
|
||||||
end
|
end
|
||||||
|
|
|
@ -723,6 +723,12 @@ defp update_credentials_request do
|
||||||
description:
|
description:
|
||||||
"Number of days after which statuses will be deleted. Set to -1 to disable."
|
"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{
|
accepts_direct_messages_from: %Schema{
|
||||||
type: :string,
|
type: :string,
|
||||||
enum: [
|
enum: [
|
||||||
|
@ -754,6 +760,7 @@ defp update_credentials_request do
|
||||||
discoverable: false,
|
discoverable: false,
|
||||||
actor_type: "Person",
|
actor_type: "Person",
|
||||||
status_ttl_days: 30,
|
status_ttl_days: 30,
|
||||||
|
permit_followback: true,
|
||||||
accepts_direct_messages_from: "everybody"
|
accepts_direct_messages_from: "everybody"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,7 +122,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
|
||||||
nodeinfo: %Schema{type: :object, nullable: true}
|
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{
|
source: %Schema{
|
||||||
|
@ -224,7 +225,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
|
||||||
# XXX: nodeinfo schema
|
# XXX: nodeinfo schema
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"status_ttl_days" => nil
|
"status_ttl_days" => nil,
|
||||||
|
"permit_followback" => true
|
||||||
},
|
},
|
||||||
"source" => %{
|
"source" => %{
|
||||||
"fields" => [],
|
"fields" => [],
|
||||||
|
|
|
@ -222,6 +222,7 @@ def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _p
|
||||||
|> Maps.put_if_present(:language, Pleroma.Web.Gettext.normalize_locale(params[:language]))
|
|> 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(: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(:accepts_direct_messages_from, params[:accepts_direct_messages_from])
|
||||||
|
|> Maps.put_if_present(:permit_followback, params[:permit_followback])
|
||||||
|
|
||||||
# What happens here:
|
# What happens here:
|
||||||
#
|
#
|
||||||
|
|
|
@ -292,7 +292,8 @@ defp do_render("show.json", %{user: user} = opts) do
|
||||||
last_status_at: user.last_status_at,
|
last_status_at: user.last_status_at,
|
||||||
akkoma: %{
|
akkoma: %{
|
||||||
instance: render("instance.json", %{instance: instance}),
|
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
|
# Pleroma extensions
|
||||||
# Note: it's insecure to output :email but fully-qualified nickname may serve as safe stub
|
# Note: it's insecure to output :email but fully-qualified nickname may serve as safe stub
|
||||||
|
|
|
@ -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
|
|
@ -1061,6 +1061,36 @@ test "directly follows a non-locked local user" do
|
||||||
|
|
||||||
assert User.following?(follower, followed)
|
assert User.following?(follower, followed)
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
describe "unfollow/2" do
|
describe "unfollow/2" do
|
||||||
|
|
|
@ -65,7 +65,8 @@ test "Represent a user account" do
|
||||||
},
|
},
|
||||||
favicon: nil
|
favicon: nil
|
||||||
},
|
},
|
||||||
status_ttl_days: 5
|
status_ttl_days: 5,
|
||||||
|
permit_followback: false
|
||||||
},
|
},
|
||||||
avatar: "http://localhost:4001/images/avi.png",
|
avatar: "http://localhost:4001/images/avi.png",
|
||||||
avatar_static: "http://localhost:4001/images/avi.png",
|
avatar_static: "http://localhost:4001/images/avi.png",
|
||||||
|
@ -248,7 +249,8 @@ test "Represent a Service(bot) account" do
|
||||||
favicon: "http://localhost:4001/favicon.png",
|
favicon: "http://localhost:4001/favicon.png",
|
||||||
nodeinfo: %{version: "2.0"}
|
nodeinfo: %{version: "2.0"}
|
||||||
},
|
},
|
||||||
status_ttl_days: nil
|
status_ttl_days: nil,
|
||||||
|
permit_followback: false
|
||||||
},
|
},
|
||||||
pleroma: %{
|
pleroma: %{
|
||||||
ap_id: user.ap_id,
|
ap_id: user.ap_id,
|
||||||
|
|
Loading…
Reference in a new issue