Merge branch 'followback' into develop
This commit is contained in:
		
						commit
						0ed815b8a1
					
				
					 11 changed files with 105 additions and 11 deletions
				
			
		| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 user’s 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:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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:
 | 
			
		||||
    #
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
    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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue