From 71ef9f9519e2617a0c05d7447bbc406ae4a8d849 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sat, 17 Aug 2024 16:36:27 +0200 Subject: [PATCH 1/7] Allow providing avatar/header descriptions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/user.ex | 28 ++++++++++++++++--- .../api_spec/operations/account_operation.ex | 10 +++++++ .../controllers/account_controller.ex | 2 ++ .../web/mastodon_api/views/account_view.ex | 10 ++++++- 4 files changed, 45 insertions(+), 5 deletions(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index c6c536943..f443b64ae 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -589,13 +589,21 @@ defmodule Pleroma.User do |> 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, nil)) + ) + |> put_change_if_present( + :banner, + &put_upload(&1, :banner, Map.get(params, :header_description, nil)) + ) |> 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 +682,25 @@ 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 maybe_update_image_description(changeset, image_field, 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 + e -> changeset + end + end + def update_as_admin_changeset(struct, params) do struct |> update_changeset(params) diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index d9614bc48..21a779dcb 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -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: %{ diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 54d46c86b..2302d6ed8 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -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: # diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index 6976ca6e5..bd8af265a 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -220,8 +220,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, @@ -323,7 +325,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do background_image: image_url(user.background) |> MediaProxy.url(), accepts_chat_messages: user.accepts_chat_messages, favicon: favicon - } + }, + avatar_description: avatar_description, + header_description: header_description } |> maybe_put_role(user, opts[:for]) |> maybe_put_settings(user, opts[:for], opts) @@ -346,6 +350,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, From 681765669c5b5bdc92079357d76c859e60bb49d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sat, 17 Aug 2024 17:02:44 +0200 Subject: [PATCH 2/7] Add test for avatar description MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/user.ex | 2 +- lib/pleroma/web/api_spec/schemas/account.ex | 4 ++ .../mastodon_api/update_credentials_test.exs | 42 +++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index f443b64ae..c3cb72fab 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -697,7 +697,7 @@ defmodule Pleroma.User do {:ok, object} <- Object.update_data(object, %{"name" => description}) do put_change(changeset, image_field, object.data) else - e -> changeset + _ -> changeset end end diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex index 8aeb821a8..32a0dd6cb 100644 --- a/lib/pleroma/web/api_spec/schemas/account.ex +++ b/lib/pleroma/web/api_spec/schemas/account.ex @@ -148,10 +148,13 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do } } } + avatar_description: %Schema{type: :string}, + header_description: %Schema{type: :string} }, 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, diff --git a/test/pleroma/web/mastodon_api/update_credentials_test.exs b/test/pleroma/web/mastodon_api/update_credentials_test.exs index bea0cae69..28d3b00db 100644 --- a/test/pleroma/web/mastodon_api/update_credentials_test.exs +++ b/test/pleroma/web/mastodon_api/update_credentials_test.exs @@ -430,6 +430,48 @@ 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 "Strip / from upload files", %{user: user, conn: conn} do new_image = %Plug.Upload{ content_type: "image/jpeg", From 071452a5d5e4e8d38d9c31bad171085574327fee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sat, 17 Aug 2024 17:03:12 +0200 Subject: [PATCH 3/7] Update changelog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- changelog.d/profile-image-descriptions.add | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/profile-image-descriptions.add diff --git a/changelog.d/profile-image-descriptions.add b/changelog.d/profile-image-descriptions.add new file mode 100644 index 000000000..85cc48083 --- /dev/null +++ b/changelog.d/profile-image-descriptions.add @@ -0,0 +1 @@ +Allow providing avatar/header descriptions \ No newline at end of file From 855c5a234f4ca743303f1b88974665d7b9f58684 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sat, 17 Aug 2024 17:05:47 +0200 Subject: [PATCH 4/7] Update docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- .../development/API/differences_in_mastoapi_responses.md | 9 ++++++++- lib/pleroma/web/api_spec/schemas/account.ex | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/development/API/differences_in_mastoapi_responses.md b/docs/development/API/differences_in_mastoapi_responses.md index 41464e802..114d6e32d 100644 --- a/docs/development/API/differences_in_mastoapi_responses.md +++ b/docs/development/API/differences_in_mastoapi_responses.md @@ -97,13 +97,18 @@ Endpoints which accept `with_relationships` parameter: - `/api/v1/accounts/:id/following` - `/api/v1/mutes` +Has these additional fields: + +- `avatar_description`: string, image description for user avatar, defaults to empty string +- `header_description`: string, image description for user banner, defaults to empty string + Has these additional fields under the `pleroma` object: - `ap_id`: nullable URL string, ActivityPub id of the user - `background_image`: nullable URL string, background image of 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/ -- `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 - `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 @@ -255,6 +260,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. diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex index 32a0dd6cb..3f2310df9 100644 --- a/lib/pleroma/web/api_spec/schemas/account.ex +++ b/lib/pleroma/web/api_spec/schemas/account.ex @@ -147,7 +147,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do } } } - } + }, avatar_description: %Schema{type: :string}, header_description: %Schema{type: :string} }, From c802f3b7f61e1c4bbe2f4eec757802e30f88b6a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sat, 17 Aug 2024 19:58:32 +0200 Subject: [PATCH 5/7] Validate media description length MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/user.ex | 24 ++++++++++++++--- .../controllers/account_controller.ex | 6 +++++ .../mastodon_api/update_credentials_test.exs | 27 +++++++++++++++++++ 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index c3cb72fab..517009253 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -586,16 +586,18 @@ 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, Map.get(params, :avatar_description, nil)) + &put_upload(&1, :avatar, Map.get(params, :avatar_description)) ) |> put_change_if_present( :banner, - &put_upload(&1, :banner, Map.get(params, :header_description, nil)) + &put_upload(&1, :banner, Map.get(params, :header_description)) ) |> put_change_if_present(:background, &put_upload(&1, :background)) |> put_change_if_present( @@ -689,7 +691,20 @@ defmodule Pleroma.User do end end - defp maybe_update_image_description(changeset, image_field, description) do + 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)}, @@ -697,10 +712,13 @@ defmodule Pleroma.User do {: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) diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 2302d6ed8..68157b0c4 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -279,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") diff --git a/test/pleroma/web/mastodon_api/update_credentials_test.exs b/test/pleroma/web/mastodon_api/update_credentials_test.exs index 28d3b00db..97ad2e849 100644 --- a/test/pleroma/web/mastodon_api/update_credentials_test.exs +++ b/test/pleroma/web/mastodon_api/update_credentials_test.exs @@ -472,6 +472,33 @@ defmodule Pleroma.Web.MastodonAPI.UpdateCredentialsTest do 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", From 3498662712c088cf578e7241c12aa1e5da42745a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sat, 17 Aug 2024 19:59:39 +0200 Subject: [PATCH 6/7] Move new fields to pleroma object MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- docs/development/API/differences_in_mastoapi_responses.md | 7 ++----- lib/pleroma/web/api_spec/schemas/account.ex | 8 ++++---- lib/pleroma/web/mastodon_api/views/account_view.ex | 8 ++++---- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/docs/development/API/differences_in_mastoapi_responses.md b/docs/development/API/differences_in_mastoapi_responses.md index 114d6e32d..22a26b77b 100644 --- a/docs/development/API/differences_in_mastoapi_responses.md +++ b/docs/development/API/differences_in_mastoapi_responses.md @@ -97,11 +97,6 @@ Endpoints which accept `with_relationships` parameter: - `/api/v1/accounts/:id/following` - `/api/v1/mutes` -Has these additional fields: - -- `avatar_description`: string, image description for user avatar, defaults to empty string -- `header_description`: string, image description for user banner, defaults to empty string - Has these additional fields under the `pleroma` object: - `ap_id`: nullable URL string, ActivityPub id of the user @@ -125,6 +120,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 diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex index 3f2310df9..1f73ef60c 100644 --- a/lib/pleroma/web/api_spec/schemas/account.ex +++ b/lib/pleroma/web/api_spec/schemas/account.ex @@ -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{ @@ -147,9 +149,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do } } } - }, - avatar_description: %Schema{type: :string}, - header_description: %Schema{type: :string} + } }, example: %{ "acct" => "foobar", diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index bd8af265a..0643b8f14 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -324,10 +324,10 @@ 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 - }, - avatar_description: avatar_description, - header_description: header_description + favicon: favicon, + avatar_description: avatar_description, + header_description: header_description + } } |> maybe_put_role(user, opts[:for]) |> maybe_put_settings(user, opts[:for], opts) From 917ac89b4f944a80b1d168fd07d94c762ee04ed9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sat, 17 Aug 2024 20:01:25 +0200 Subject: [PATCH 7/7] Update tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- test/pleroma/web/mastodon_api/views/account_view_test.exs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/pleroma/web/mastodon_api/views/account_view_test.exs b/test/pleroma/web/mastodon_api/views/account_view_test.exs index dca64853d..0301a4cca 100644 --- a/test/pleroma/web/mastodon_api/views/account_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/account_view_test.exs @@ -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: "" } }