Merge branch 'followback' into develop

This commit is contained in:
FloatingGhost 2024-02-16 13:27:40 +00:00
commit 0ed815b8a1
11 changed files with 105 additions and 11 deletions

View file

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

View file

@ -121,6 +121,12 @@ Has these additional fields under the `pleroma` object:
- `notification_settings`: object, can be absent. See `/api/v1/pleroma/notification_settings` for the parameters/keys returned.
- `favicon`: nullable URL string, Favicon image of the user's instance
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
Has these additional fields under the `pleroma` object:

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 @@ def update_changeset(struct, params \\ %{}) do
:actor_type,
:disclose_client,
:status_ttl_days,
:permit_followback,
:accepts_direct_messages_from
]
)
@ -972,16 +974,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
@ -1331,6 +1338,13 @@ def get_friends_ids(%User{} = user, page \\ nil) 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 @@ 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

@ -723,6 +723,12 @@ defp update_credentials_request 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 @@ defp update_credentials_request do
discoverable: false,
actor_type: "Person",
status_ttl_days: 30,
permit_followback: true,
accepts_direct_messages_from: "everybody"
}
}

View file

@ -112,7 +112,18 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
akkoma: %Schema{
type: :object,
properties: %{
note_ttl_days: %Schema{type: :integer}
instance: %Schema{
type: :object,
nullable: true,
properties: %{
name: %Schema{type: :string},
favicon: %Schema{type: :string, format: :uri, nullable: true},
# XXX: proper nodeinfo schema
nodeinfo: %Schema{type: :object, nullable: true}
}
},
status_ttl_days: %Schema{type: :integer, nullable: true},
permit_followback: %Schema{type: :boolean}
}
},
source: %Schema{
@ -205,6 +216,18 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
"pleroma-fe" => %{}
}
},
"akkoma" => %{
"instance" => %{
"name" => "ihatebeinga.live",
"favicon" => "https://ihatebeinga.live/favicon.png",
"nodeinfo" =>
%{
# XXX: nodeinfo schema
}
},
"status_ttl_days" => nil,
"permit_followback" => true
},
"source" => %{
"fields" => [],
"note" => "foobar",

View file

@ -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(: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 @@ defp do_render("show.json", %{user: user} = opts) 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 @@ 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

@ -65,7 +65,8 @@ test "Represent a user account" 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 @@ test "Represent a Service(bot) account" 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,