diff --git a/FEDERATION.md b/FEDERATION.md index d5a176807b2..0ac44afc3cd 100644 --- a/FEDERATION.md +++ b/FEDERATION.md @@ -67,3 +67,4 @@ The following table summarizes those limits. | Account `attributionDomains` | 256 | List will be truncated | | Account aliases (actor `alsoKnownAs`) | 256 | List will be truncated | | Custom emoji shortcode (`Emoji` `name`) | 2048 | Emoji will be rejected | +| Media and avatar/header descriptions (`name`/`summary`) | 1500 | Description will be truncated | diff --git a/app/models/account.rb b/app/models/account.rb index 32f6e39840f..7623e2398cc 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -9,6 +9,7 @@ # also_known_as :string is an Array # attribution_domains :string default([]), is an Array # avatar_content_type :string +# avatar_description :string default(""), not null # avatar_file_name :string # avatar_file_size :integer # avatar_remote_url :string @@ -23,6 +24,7 @@ # followers_url :string default(""), not null # following_url :string default(""), not null # header_content_type :string +# header_description :string default(""), not null # header_file_name :string # header_file_size :integer # header_remote_url :string default(""), not null diff --git a/app/models/concerns/account/avatar.rb b/app/models/concerns/account/avatar.rb index 0be46788818..945ba3592cd 100644 --- a/app/models/concerns/account/avatar.rb +++ b/app/models/concerns/account/avatar.rb @@ -24,6 +24,8 @@ module Account::Avatar validates_attachment_content_type :avatar, content_type: AVATAR_IMAGE_MIME_TYPES validates_attachment_size :avatar, less_than: AVATAR_LIMIT remotable_attachment :avatar, AVATAR_LIMIT, suppress_errors: false + + validates :avatar_description, length: { maximum: MediaAttachment::MAX_DESCRIPTION_LENGTH } end def avatar_original_url diff --git a/app/models/concerns/account/header.rb b/app/models/concerns/account/header.rb index 066c42cb6cd..1b70715a9e7 100644 --- a/app/models/concerns/account/header.rb +++ b/app/models/concerns/account/header.rb @@ -25,6 +25,8 @@ module Account::Header validates_attachment_content_type :header, content_type: HEADER_IMAGE_MIME_TYPES validates_attachment_size :header, less_than: HEADER_LIMIT remotable_attachment :header, HEADER_LIMIT, suppress_errors: false + + validates :header_description, length: { maximum: MediaAttachment::MAX_DESCRIPTION_LENGTH } end def header_original_url diff --git a/app/serializers/rest/account_serializer.rb b/app/serializers/rest/account_serializer.rb index 26715dd1032..38ddd6ac4dd 100644 --- a/app/serializers/rest/account_serializer.rb +++ b/app/serializers/rest/account_serializer.rb @@ -7,7 +7,7 @@ class REST::AccountSerializer < ActiveModel::Serializer # Please update `app/javascript/mastodon/api_types/accounts.ts` when making changes to the attributes attributes :id, :username, :acct, :display_name, :locked, :bot, :discoverable, :indexable, :group, :created_at, - :note, :url, :uri, :avatar, :avatar_static, :header, :header_static, + :note, :url, :uri, :avatar, :avatar_static, :avatar_description, :header, :header_static, :header_description, :followers_count, :following_count, :statuses_count, :last_status_at, :hide_collections has_one :moved_to_account, key: :moved, serializer: REST::AccountSerializer, if: :moved_and_not_nested? diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb index 6f4aa2fdb6e..efdaa269747 100644 --- a/app/services/activitypub/process_account_service.rb +++ b/app/services/activitypub/process_account_service.rb @@ -142,14 +142,18 @@ class ActivityPub::ProcessAccountService < BaseService def set_fetchable_attributes! begin - @account.avatar_remote_url = image_url('icon') || '' unless skip_download? + avatar_url, avatar_description = image_url_and_description('icon') + @account.avatar_remote_url = avatar_url || '' unless skip_download? @account.avatar = nil if @account.avatar_remote_url.blank? + @account.avatar_description = avatar_description || '' rescue Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS RedownloadAvatarWorker.perform_in(rand(30..600).seconds, @account.id) end begin - @account.header_remote_url = image_url('image') || '' unless skip_download? + header_url, header_description = image_url_and_description('image') + @account.header_remote_url = header_url || '' unless skip_download? @account.header = nil if @account.header_remote_url.blank? + @account.header_description = header_description || '' rescue Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS RedownloadHeaderWorker.perform_in(rand(30..600).seconds, @account.id) end @@ -214,7 +218,7 @@ class ActivityPub::ProcessAccountService < BaseService end end - def image_url(key) + def image_url_and_description(key) value = first_of_value(@json[key]) return if value.nil? @@ -224,9 +228,21 @@ class ActivityPub::ProcessAccountService < BaseService return if value.nil? end - value = first_of_value(value['url']) if value.is_a?(Hash) && value['type'] == 'Image' - value = value['href'] if value.is_a?(Hash) - value if value.is_a?(String) + if value.is_a?(Hash) && value['type'] == 'Image' + url = first_of_value(value['url']) + url = url['href'] if url.is_a?(Hash) + description = value['summary'].presence || value['name'].presence + description = description.strip[0...MediaAttachment::MAX_DESCRIPTION_LENGTH] if description.present? + else + url = value + end + + url = url['href'] if url.is_a?(Hash) + + url = nil unless url.is_a?(String) + description = nil unless description.is_a?(String) + + [url, description] end def public_key diff --git a/db/migrate/20260127141459_add_avatar_description_to_accounts.rb b/db/migrate/20260127141459_add_avatar_description_to_accounts.rb new file mode 100644 index 00000000000..831342c50cb --- /dev/null +++ b/db/migrate/20260127141459_add_avatar_description_to_accounts.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddAvatarDescriptionToAccounts < ActiveRecord::Migration[8.0] + def change + add_column :accounts, :avatar_description, :string, null: false, default: '' + end +end diff --git a/db/migrate/20260127141820_add_header_description_to_accounts.rb b/db/migrate/20260127141820_add_header_description_to_accounts.rb new file mode 100644 index 00000000000..74d03d5c851 --- /dev/null +++ b/db/migrate/20260127141820_add_header_description_to_accounts.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddHeaderDescriptionToAccounts < ActiveRecord::Migration[8.0] + def change + add_column :accounts, :header_description, :string, null: false, default: '' + end +end diff --git a/db/schema.rb b/db/schema.rb index 8801882808d..9080de8fb89 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2026_01_19_153538) do +ActiveRecord::Schema[8.0].define(version: 2026_01_27_141820) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" @@ -201,6 +201,8 @@ ActiveRecord::Schema[8.0].define(version: 2026_01_19_153538) do t.string "following_url", default: "", null: false t.integer "id_scheme", default: 1 t.integer "feature_approval_policy", default: 0, null: false + t.string "avatar_description", default: "", null: false + t.string "header_description", default: "", null: false t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin t.index "lower((username)::text), COALESCE(lower((domain)::text), ''::text)", name: "index_accounts_on_username_and_domain_lower", unique: true t.index ["domain", "id"], name: "index_accounts_on_domain_and_id"