Merge branch 'profile-image-descriptions' into 'develop'

Allow providing avatar/header descriptions

See merge request pleroma/pleroma!4227
This commit is contained in:
marcin mikołajczak 2024-09-14 17:46:33 +00:00
commit c1a1150888
9 changed files with 155 additions and 9 deletions

View file

@ -0,0 +1 @@
Allow providing avatar/header descriptions

View file

@ -121,6 +121,8 @@ 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.
- `accepts_chat_messages`: boolean, but can be null if we don't have that information about a user
- `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
### Source
@ -256,6 +258,8 @@ Additional parameters can be added to the JSON body/Form data:
- `actor_type` - the type of this account.
- `accepts_chat_messages` - if false, this account will reject all chat messages.
- `language` - user's preferred language for receiving emails (digest, confirmation, etc.)
- `avatar_description` - image description for user avatar
- `header_description` - image description for user banner
All images (avatar, banner and background) can be reset to the default by sending an empty string ("") instead of a file.

View file

@ -586,16 +586,26 @@ defmodule Pleroma.User do
|> validate_length(:bio, max: bio_limit)
|> validate_length(:name, min: 1, max: name_limit)
|> validate_inclusion(:actor_type, Pleroma.Constants.allowed_user_actor_types())
|> validate_image_description(:avatar_description, params)
|> validate_image_description(:header_description, params)
|> put_fields()
|> put_emoji()
|> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)})
|> put_change_if_present(:avatar, &put_upload(&1, :avatar))
|> put_change_if_present(:banner, &put_upload(&1, :banner))
|> put_change_if_present(
:avatar,
&put_upload(&1, :avatar, Map.get(params, :avatar_description))
)
|> put_change_if_present(
:banner,
&put_upload(&1, :banner, Map.get(params, :header_description))
)
|> put_change_if_present(:background, &put_upload(&1, :background))
|> put_change_if_present(
:pleroma_settings_store,
&{:ok, Map.merge(struct.pleroma_settings_store, &1)}
)
|> maybe_update_image_description(:avatar, Map.get(params, :avatar_description))
|> maybe_update_image_description(:banner, Map.get(params, :header_description))
|> validate_fields(false)
end
@ -674,13 +684,41 @@ defmodule Pleroma.User do
end
end
defp put_upload(value, type) do
defp put_upload(value, type, description \\ nil) do
with %Plug.Upload{} <- value,
{:ok, object} <- ActivityPub.upload(value, type: type) do
{:ok, object} <- ActivityPub.upload(value, type: type, description: description) do
{:ok, object.data}
end
end
defp validate_image_description(changeset, key, params) do
description_limit = Config.get([:instance, :description_limit], 5_000)
description = Map.get(params, key)
if is_binary(description) and String.length(description) > description_limit do
changeset
|> add_error(key, "#{key} is too long")
else
changeset
end
end
defp maybe_update_image_description(changeset, image_field, description)
when is_binary(description) do
with {:image_missing, true} <- {:image_missing, not changed?(changeset, image_field)},
{:existing_image, %{"id" => id}} <-
{:existing_image, Map.get(changeset.data, image_field)},
{:object, %Object{} = object} <- {:object, Object.get_by_ap_id(id)},
{:ok, object} <- Object.update_data(object, %{"name" => description}) do
put_change(changeset, image_field, object.data)
else
{:description_too_long, true} -> {:error}
_ -> changeset
end
end
defp maybe_update_image_description(changeset, _, _), do: changeset
def update_as_admin_changeset(struct, params) do
struct
|> update_changeset(params)

View file

@ -813,6 +813,16 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
allOf: [BooleanLike],
nullable: true,
description: "User's birthday will be visible"
},
avatar_description: %Schema{
type: :string,
nullable: true,
description: "Avatar image description."
},
header_description: %Schema{
type: :string,
nullable: true,
description: "Header image description."
}
},
example: %{

View file

@ -111,7 +111,9 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
format: :uri,
nullable: true,
description: "Favicon image of the user's instance"
}
},
avatar_description: %Schema{type: :string},
header_description: %Schema{type: :string}
}
},
source: %Schema{
@ -152,6 +154,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
example: %{
"acct" => "foobar",
"avatar" => "https://mypleroma.com/images/avi.png",
"avatar_description" => "",
"avatar_static" => "https://mypleroma.com/images/avi.png",
"bot" => false,
"created_at" => "2020-03-24T13:05:58.000Z",
@ -162,6 +165,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
"followers_count" => 0,
"following_count" => 1,
"header" => "https://mypleroma.com/images/banner.png",
"header_description" => "",
"header_static" => "https://mypleroma.com/images/banner.png",
"id" => "9tKi3esbG7OQgZ2920",
"locked" => false,

View file

@ -232,6 +232,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|> Maps.put_if_present(:is_discoverable, params[:discoverable])
|> Maps.put_if_present(:birthday, params[:birthday])
|> 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])
# What happens here:
#
@ -277,6 +279,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
{:error, %Ecto.Changeset{errors: [{:name, {_, _}} | _]}} ->
render_error(conn, :request_entity_too_large, "Name is too long")
{:error, %Ecto.Changeset{errors: [{:avatar_description, {_, _}} | _]}} ->
render_error(conn, :request_entity_too_large, "Avatar description is too long")
{:error, %Ecto.Changeset{errors: [{:header_description, {_, _}} | _]}} ->
render_error(conn, :request_entity_too_large, "Banner description is too long")
{:error, %Ecto.Changeset{errors: [{:fields, {"invalid", _}} | _]}} ->
render_error(conn, :request_entity_too_large, "One or more field entries are too long")

View file

@ -219,8 +219,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
avatar = User.avatar_url(user) |> MediaProxy.url()
avatar_static = User.avatar_url(user) |> MediaProxy.preview_url(static: true)
avatar_description = image_description(user.avatar)
header = User.banner_url(user) |> MediaProxy.url()
header_static = User.banner_url(user) |> MediaProxy.preview_url(static: true)
header_description = image_description(user.banner)
following_count =
if !user.hide_follows_count or !user.hide_follows or self,
@ -321,7 +323,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
skip_thread_containment: user.skip_thread_containment,
background_image: image_url(user.background) |> MediaProxy.url(),
accepts_chat_messages: user.accepts_chat_messages,
favicon: favicon
favicon: favicon,
avatar_description: avatar_description,
header_description: header_description
}
}
|> maybe_put_role(user, opts[:for])
@ -345,6 +349,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
defp username_from_nickname(_), do: nil
defp image_description(%{"name" => name}), do: name
defp image_description(_), do: ""
defp maybe_put_follow_requests_count(
data,
%User{id: user_id} = user,

View file

@ -430,6 +430,75 @@ defmodule Pleroma.Web.MastodonAPI.UpdateCredentialsTest do
assert :ok == File.rm(Path.absname("test/tmp/large_binary.data"))
end
test "adds avatar description with a new avatar", %{user: user, conn: conn} do
new_avatar = %Plug.Upload{
content_type: "image/jpeg",
path: Path.absname("test/fixtures/image.jpg"),
filename: "an_image.jpg"
}
res =
patch(conn, "/api/v1/accounts/update_credentials", %{
"avatar" => new_avatar,
"avatar_description" => "me and pleroma tan"
})
assert json_response_and_validate_schema(res, 200)
user = User.get_by_id(user.id)
assert user.avatar["name"] == "me and pleroma tan"
end
test "adds avatar description to existing avatar", %{user: user, conn: conn} do
new_avatar = %Plug.Upload{
content_type: "image/jpeg",
path: Path.absname("test/fixtures/image.jpg"),
filename: "an_image.jpg"
}
assert user.avatar == %{}
conn
|> patch("/api/v1/accounts/update_credentials", %{"avatar" => new_avatar})
assert conn
|> assign(:user, User.get_by_id(user.id))
|> patch("/api/v1/accounts/update_credentials", %{
"avatar_description" => "me and pleroma tan"
})
|> json_response_and_validate_schema(200)
user = User.get_by_id(user.id)
assert user.avatar["name"] == "me and pleroma tan"
end
test "limit", %{user: user, conn: conn} do
new_header = %Plug.Upload{
content_type: "image/jpeg",
path: Path.absname("test/fixtures/image.jpg"),
filename: "an_image.jpg"
}
assert user.banner == %{}
conn
|> patch("/api/v1/accounts/update_credentials", %{"header" => new_header})
description_limit = Config.get([:instance, :description_limit], 100)
description = String.duplicate(".", description_limit + 1)
conn =
conn
|> assign(:user, User.get_by_id(user.id))
|> patch("/api/v1/accounts/update_credentials", %{
"header_description" => description
})
assert %{"error" => "Banner description is too long"} =
json_response_and_validate_schema(conn, 413)
end
test "Strip / from upload files", %{user: user, conn: conn} do
new_image = %Plug.Upload{
content_type: "image/jpeg",

View file

@ -96,7 +96,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
hide_follows_count: false,
relationship: %{},
skip_thread_containment: false,
accepts_chat_messages: nil
accepts_chat_messages: nil,
avatar_description: "",
header_description: ""
}
}
@ -340,7 +342,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
hide_follows_count: false,
relationship: %{},
skip_thread_containment: false,
accepts_chat_messages: nil
accepts_chat_messages: nil,
avatar_description: "",
header_description: ""
}
}