mirror of
https://git.pleroma.social/pleroma/pleroma.git
synced 2024-12-22 16:16:34 +00:00
Merge branch 'profile-image-descriptions' into 'develop'
Allow providing avatar/header descriptions See merge request pleroma/pleroma!4227
This commit is contained in:
commit
c1a1150888
9 changed files with 155 additions and 9 deletions
1
changelog.d/profile-image-descriptions.add
Normal file
1
changelog.d/profile-image-descriptions.add
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Allow providing avatar/header descriptions
|
|
@ -104,7 +104,7 @@ Has these additional fields under the `pleroma` object:
|
||||||
- `background_image`: nullable URL string, background image of the user
|
- `background_image`: nullable URL string, background image of the user
|
||||||
- `tags`: Lists an array of tags for the user
|
- `tags`: Lists an array of tags for the user
|
||||||
- `relationship` (object): Includes fields as documented for Mastodon API https://docs.joinmastodon.org/entities/relationship/
|
- `relationship` (object): Includes fields as documented for Mastodon API https://docs.joinmastodon.org/entities/relationship/
|
||||||
- `is_moderator`: boolean, nullable, true if user is a moderator
|
- `is_moderator`: boolean, nullable, true if user is a moderator
|
||||||
- `is_admin`: boolean, nullable, true if user is an admin
|
- `is_admin`: boolean, nullable, true if user is an admin
|
||||||
- `confirmation_pending`: boolean, true if a new user account is waiting on email confirmation to be activated
|
- `confirmation_pending`: boolean, true if a new user account is waiting on email confirmation to be activated
|
||||||
- `hide_favorites`: boolean, true when the user has hiding favorites enabled
|
- `hide_favorites`: boolean, true when the user has hiding favorites enabled
|
||||||
|
@ -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.
|
- `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
|
- `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
|
- `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
|
### Source
|
||||||
|
|
||||||
|
@ -256,6 +258,8 @@ Additional parameters can be added to the JSON body/Form data:
|
||||||
- `actor_type` - the type of this account.
|
- `actor_type` - the type of this account.
|
||||||
- `accepts_chat_messages` - if false, this account will reject all chat messages.
|
- `accepts_chat_messages` - if false, this account will reject all chat messages.
|
||||||
- `language` - user's preferred language for receiving emails (digest, confirmation, etc.)
|
- `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.
|
All images (avatar, banner and background) can be reset to the default by sending an empty string ("") instead of a file.
|
||||||
|
|
||||||
|
|
|
@ -586,16 +586,26 @@ defmodule Pleroma.User do
|
||||||
|> validate_length(:bio, max: bio_limit)
|
|> validate_length(:bio, max: bio_limit)
|
||||||
|> validate_length(:name, min: 1, max: name_limit)
|
|> validate_length(:name, min: 1, max: name_limit)
|
||||||
|> validate_inclusion(:actor_type, Pleroma.Constants.allowed_user_actor_types())
|
|> 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_fields()
|
||||||
|> put_emoji()
|
|> put_emoji()
|
||||||
|> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)})
|
|> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)})
|
||||||
|> put_change_if_present(:avatar, &put_upload(&1, :avatar))
|
|> put_change_if_present(
|
||||||
|> put_change_if_present(:banner, &put_upload(&1, :banner))
|
: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(:background, &put_upload(&1, :background))
|
||||||
|> put_change_if_present(
|
|> put_change_if_present(
|
||||||
:pleroma_settings_store,
|
:pleroma_settings_store,
|
||||||
&{:ok, Map.merge(struct.pleroma_settings_store, &1)}
|
&{: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)
|
|> validate_fields(false)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -674,13 +684,41 @@ defmodule Pleroma.User do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp put_upload(value, type) do
|
defp put_upload(value, type, description \\ nil) do
|
||||||
with %Plug.Upload{} <- value,
|
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}
|
{:ok, object.data}
|
||||||
end
|
end
|
||||||
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
|
def update_as_admin_changeset(struct, params) do
|
||||||
struct
|
struct
|
||||||
|> update_changeset(params)
|
|> update_changeset(params)
|
||||||
|
|
|
@ -813,6 +813,16 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
|
||||||
allOf: [BooleanLike],
|
allOf: [BooleanLike],
|
||||||
nullable: true,
|
nullable: true,
|
||||||
description: "User's birthday will be visible"
|
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: %{
|
example: %{
|
||||||
|
|
|
@ -111,7 +111,9 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
|
||||||
format: :uri,
|
format: :uri,
|
||||||
nullable: true,
|
nullable: true,
|
||||||
description: "Favicon image of the user's instance"
|
description: "Favicon image of the user's instance"
|
||||||
}
|
},
|
||||||
|
avatar_description: %Schema{type: :string},
|
||||||
|
header_description: %Schema{type: :string}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
source: %Schema{
|
source: %Schema{
|
||||||
|
@ -152,6 +154,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
|
||||||
example: %{
|
example: %{
|
||||||
"acct" => "foobar",
|
"acct" => "foobar",
|
||||||
"avatar" => "https://mypleroma.com/images/avi.png",
|
"avatar" => "https://mypleroma.com/images/avi.png",
|
||||||
|
"avatar_description" => "",
|
||||||
"avatar_static" => "https://mypleroma.com/images/avi.png",
|
"avatar_static" => "https://mypleroma.com/images/avi.png",
|
||||||
"bot" => false,
|
"bot" => false,
|
||||||
"created_at" => "2020-03-24T13:05:58.000Z",
|
"created_at" => "2020-03-24T13:05:58.000Z",
|
||||||
|
@ -162,6 +165,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
|
||||||
"followers_count" => 0,
|
"followers_count" => 0,
|
||||||
"following_count" => 1,
|
"following_count" => 1,
|
||||||
"header" => "https://mypleroma.com/images/banner.png",
|
"header" => "https://mypleroma.com/images/banner.png",
|
||||||
|
"header_description" => "",
|
||||||
"header_static" => "https://mypleroma.com/images/banner.png",
|
"header_static" => "https://mypleroma.com/images/banner.png",
|
||||||
"id" => "9tKi3esbG7OQgZ2920",
|
"id" => "9tKi3esbG7OQgZ2920",
|
||||||
"locked" => false,
|
"locked" => false,
|
||||||
|
|
|
@ -232,6 +232,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
||||||
|> Maps.put_if_present(:is_discoverable, params[:discoverable])
|
|> Maps.put_if_present(:is_discoverable, params[:discoverable])
|
||||||
|> Maps.put_if_present(:birthday, params[:birthday])
|
|> Maps.put_if_present(:birthday, params[:birthday])
|
||||||
|> 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(:avatar_description, params[:avatar_description])
|
||||||
|
|> Maps.put_if_present(:header_description, params[:header_description])
|
||||||
|
|
||||||
# What happens here:
|
# What happens here:
|
||||||
#
|
#
|
||||||
|
@ -277,6 +279,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
||||||
{:error, %Ecto.Changeset{errors: [{:name, {_, _}} | _]}} ->
|
{:error, %Ecto.Changeset{errors: [{:name, {_, _}} | _]}} ->
|
||||||
render_error(conn, :request_entity_too_large, "Name is too long")
|
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", _}} | _]}} ->
|
{:error, %Ecto.Changeset{errors: [{:fields, {"invalid", _}} | _]}} ->
|
||||||
render_error(conn, :request_entity_too_large, "One or more field entries are too long")
|
render_error(conn, :request_entity_too_large, "One or more field entries are too long")
|
||||||
|
|
||||||
|
|
|
@ -219,8 +219,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
||||||
|
|
||||||
avatar = User.avatar_url(user) |> MediaProxy.url()
|
avatar = User.avatar_url(user) |> MediaProxy.url()
|
||||||
avatar_static = User.avatar_url(user) |> MediaProxy.preview_url(static: true)
|
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 = User.banner_url(user) |> MediaProxy.url()
|
||||||
header_static = User.banner_url(user) |> MediaProxy.preview_url(static: true)
|
header_static = User.banner_url(user) |> MediaProxy.preview_url(static: true)
|
||||||
|
header_description = image_description(user.banner)
|
||||||
|
|
||||||
following_count =
|
following_count =
|
||||||
if !user.hide_follows_count or !user.hide_follows or self,
|
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,
|
skip_thread_containment: user.skip_thread_containment,
|
||||||
background_image: image_url(user.background) |> MediaProxy.url(),
|
background_image: image_url(user.background) |> MediaProxy.url(),
|
||||||
accepts_chat_messages: user.accepts_chat_messages,
|
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])
|
|> maybe_put_role(user, opts[:for])
|
||||||
|
@ -345,6 +349,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
||||||
|
|
||||||
defp username_from_nickname(_), do: nil
|
defp username_from_nickname(_), do: nil
|
||||||
|
|
||||||
|
defp image_description(%{"name" => name}), do: name
|
||||||
|
|
||||||
|
defp image_description(_), do: ""
|
||||||
|
|
||||||
defp maybe_put_follow_requests_count(
|
defp maybe_put_follow_requests_count(
|
||||||
data,
|
data,
|
||||||
%User{id: user_id} = user,
|
%User{id: user_id} = user,
|
||||||
|
|
|
@ -430,6 +430,75 @@ defmodule Pleroma.Web.MastodonAPI.UpdateCredentialsTest do
|
||||||
assert :ok == File.rm(Path.absname("test/tmp/large_binary.data"))
|
assert :ok == File.rm(Path.absname("test/tmp/large_binary.data"))
|
||||||
end
|
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
|
test "Strip / from upload files", %{user: user, conn: conn} do
|
||||||
new_image = %Plug.Upload{
|
new_image = %Plug.Upload{
|
||||||
content_type: "image/jpeg",
|
content_type: "image/jpeg",
|
||||||
|
|
|
@ -96,7 +96,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
|
||||||
hide_follows_count: false,
|
hide_follows_count: false,
|
||||||
relationship: %{},
|
relationship: %{},
|
||||||
skip_thread_containment: false,
|
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,
|
hide_follows_count: false,
|
||||||
relationship: %{},
|
relationship: %{},
|
||||||
skip_thread_containment: false,
|
skip_thread_containment: false,
|
||||||
accepts_chat_messages: nil
|
accepts_chat_messages: nil,
|
||||||
|
avatar_description: "",
|
||||||
|
header_description: ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue