Merge pull request 'Allow providing avatar/header descriptions' (#1034) from mkljczk/akkoma:avatar-header-descriptions into develop
All checks were successful
ci/woodpecker/push/docs Pipeline was successful
ci/woodpecker/push/publish/4 Pipeline was successful
ci/woodpecker/push/publish/1 Pipeline was successful
ci/woodpecker/push/publish/2 Pipeline was successful

Reviewed-on: #1034
Reviewed-by: Oneric <oneric@noreply.akkoma>
This commit is contained in:
Oneric 2026-04-04 12:13:45 +00:00
commit ee5b6f250f
13 changed files with 471 additions and 23 deletions

View file

@ -8,6 +8,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Added
- `{POST,PUT} api/v1/lists` now accepts the `exclusive` parameter from Mastodon allowing followed users in the list to be removed from the home timeline
- User profile media now (can) have federated alt text; to this end:
- Mastodon-compatible `avatar_description` and `header_description` parameters are added to account API responses and as input for `PATCH /api/v1/accounts/update_credentials`
- `pleroma.background_image_descripption` is added to account API responses
- `pleroma_background_image_descripption` is added as a new parameter to `PATCH /api/v1/accounts/update_credentials`
### Fixed
- fix date-time format in `* /api/v1/markers` to strictly conform to Mastodons ISO 8061 subset

View file

@ -251,6 +251,7 @@ Additional parameters can be added to the JSON body/Form data:
- `allow_following_move` - if true, allows automatically follow moved following accounts
- `also_known_as` - array of ActivityPub IDs, needed for following move
- `pleroma_background_image` - sets the background image of the user. Can be set to "" (an empty string) to reset.
- `pleroma_background_image_description` - sets plaintext alt text for the background image of the user. Can be set to "" (an empty string) to delete.
- `discoverable` - if true, external services (search bots) etc. are allowed to index / list the account (regardless of this setting, user will still appear in regular search results).
- `actor_type` - the type of this account.
- `language` - user's preferred language for receiving emails (digest, confirmation, etc.)

View file

@ -398,6 +398,12 @@ defmodule Pleroma.User do
end
end
def image_description(image, default \\ "")
def image_description(%{"summary" => summary}, _default), do: summary
def image_description(%{"name" => name}, _default), do: name
def image_description(_, default), do: default
# Should probably be renamed or removed
@spec ap_id(User.t()) :: String.t()
def ap_id(%User{nickname: nickname}), do: "#{Endpoint.url()}/users/#{nickname}"
@ -566,9 +572,9 @@ 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(:background, &put_upload(&1, :background))
|> put_media_update(params, :avatar, :avatar_description)
|> put_media_update(params, :banner, :header_description)
|> put_media_update(params, :background, :background_description)
|> put_change_if_present(
:pleroma_settings_store,
&{:ok, Map.merge(struct.pleroma_settings_store, &1)}
@ -635,10 +641,38 @@ defmodule Pleroma.User do
with {:ok, value} <- fetch_change(changeset, map_field),
{:ok, new_value} <- value_function.(value) do
put_change(changeset, map_field, new_value)
else
_ -> changeset
end
end
defp validate_image_description(changeset, key, description) do
description_limit = Config.get([:instance, :description_limit])
if is_binary(description) and String.length(description) > description_limit do
add_error(changeset, key, "#{key} is too long")
else
changeset
end
end
defp put_new_media(changeset, media_key, new_image, new_description) do
# copy old description if necessary
description =
if is_binary(new_description) do
new_description
else
old_image = Map.get(changeset.data, media_key)
image_description(old_image, nil)
end
with %Plug.Upload{} <- new_image,
{:ok, object} <- ActivityPub.upload(new_image, type: media_key, description: description) do
put_change(changeset, media_key, object.data)
else
{:error, :file_too_large} ->
Ecto.Changeset.validate_change(changeset, map_field, fn map_field, _value ->
[{map_field, "file is too large"}]
Ecto.Changeset.validate_change(changeset, media_key, fn media_key, _value ->
[{media_key, "file is too large"}]
end)
_ ->
@ -646,10 +680,45 @@ defmodule Pleroma.User do
end
end
defp put_upload(value, type) do
with %Plug.Upload{} <- value,
{:ok, object} <- ActivityPub.upload(value, type: type) do
{:ok, object.data}
defp maybe_update_image_description(changeset, image_field, desc_key, description)
when is_binary(description) do
with {: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
{:existing_image, _} ->
if description != "" do
add_error(
changeset,
desc_key,
"#{desc_key} needs #{image_field} to be set before or simultaneously"
)
else
changeset
end
_ ->
changeset
end
end
defp maybe_update_image_description(changeset, _, _, _), do: changeset
defp put_media_update(changeset, params, media_key, description_key) do
# We store description and image (url) in a shared JSON blob, but the API
# allows both to be updated independently (in Mastodon descriptions can also
# exist without image, but we cannot easily do this)
description_param = Map.get(params, description_key)
changeset = validate_image_description(changeset, description_key, description_param)
case fetch_change(changeset, media_key) do
{:ok, new_image} ->
put_new_media(changeset, media_key, new_image, description_param)
_ ->
maybe_update_image_description(changeset, media_key, description_key, description_param)
end
end

View file

@ -31,11 +31,22 @@ defmodule Pleroma.User.Fetcher do
defp get_actor_url(_url), do: nil
defp normalize_image(%{"url" => url}) do
defp maybe_put_description(map, %{"summary" => description}) when is_binary(description) do
Map.put(map, "name", description)
end
defp maybe_put_description(map, %{"name" => description}) when is_binary(description) do
Map.put(map, "name", description)
end
defp maybe_put_description(map, _), do: map
defp normalize_image(%{"url" => url} = data) do
%{
"type" => "Image",
"url" => [%{"href" => url}]
}
|> maybe_put_description(data)
end
defp normalize_image(urls) when is_list(urls), do: urls |> List.first() |> normalize_image()

View file

@ -108,10 +108,20 @@ defmodule Pleroma.Web.ActivityPub.UserView do
"alsoKnownAs" => user.also_known_as
}
|> maybe_put_webfinger(user)
|> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
|> Map.merge(maybe_make_image(&User.banner_url/2, "image", user))
|> Map.merge(
maybe_make_image(&User.avatar_url/2, User.image_description(user.avatar), "icon", user)
)
|> Map.merge(
maybe_make_image(&User.banner_url/2, User.image_description(user.banner), "image", user)
)
# Yes, the key is named ...Url eventhough it is a whole 'Image' object
|> Map.merge(maybe_insert_image("backgroundUrl", User.background_url(user)))
|> Map.merge(
maybe_insert_image(
"backgroundUrl",
User.background_url(user),
User.image_description(user.background)
)
)
|> Map.merge(Utils.make_json_ld_header())
end
@ -314,18 +324,20 @@ defmodule Pleroma.Web.ActivityPub.UserView do
defp maybe_put_webfinger(data, _), do: data
defp maybe_make_image(func, key, user) do
defp maybe_make_image(func, description, key, user) do
image = func.(user, no_default: true)
maybe_insert_image(key, image)
maybe_insert_image(key, image, description)
end
defp maybe_insert_image(key, image) do
defp maybe_insert_image(key, image, description) do
if image do
%{
key => %{
"type" => "Image",
"url" => image
}
key =>
%{
"type" => "Image",
"url" => image
}
|> maybe_put("name", description)
}
else
%{}

View file

@ -65,7 +65,8 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
responses: %{
200 => Operation.response("Account", "application/json", Account),
403 => Operation.response("Error", "application/json", ApiError),
413 => Operation.response("Error", "application/json", ApiError)
413 => Operation.response("Error", "application/json", ApiError),
422 => Operation.response("Error", "application/json", ApiError)
}
}
end
@ -616,12 +617,22 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
description: "Avatar image encoded using multipart/form-data",
format: :binary
},
avatar_description: %Schema{
type: :string,
nullable: true,
description: "Sets description (alt text) of the users avatar image."
},
header: %Schema{
type: :string,
nullable: true,
description: "Header image encoded using multipart/form-data",
format: :binary
},
header_description: %Schema{
type: :string,
nullable: true,
description: "Sets description (alt text) of the users header image."
},
locked: %Schema{
allOf: [BooleanLike],
nullable: true,
@ -710,6 +721,11 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
description: "Sets the background image of the user.",
format: :binary
},
pleroma_background_image_description: %Schema{
type: :string,
nullable: true,
description: "Sets description (alt text) of the users background image."
},
discoverable: %Schema{
allOf: [BooleanLike],
nullable: true,

View file

@ -21,6 +21,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
acct: %Schema{type: :string},
avatar_static: %Schema{type: :string, format: :uri},
avatar: %Schema{type: :string, format: :uri},
avatar_description: %Schema{type: :string},
bot: %Schema{type: :boolean},
created_at: %Schema{type: :string, format: "date-time"},
display_name: %Schema{type: :string},
@ -31,6 +32,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
following_count: %Schema{type: :integer},
header_static: %Schema{type: :string, format: :uri},
header: %Schema{type: :string, format: :uri},
header_description: %Schema{type: :string},
id: FlakeID,
locked: %Schema{type: :boolean},
note: %Schema{type: :string, format: :html},
@ -47,6 +49,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
description: "whether the user allows automatically follow moved following accounts"
},
background_image: %Schema{type: :string, nullable: true, format: :uri},
background_image_description: %Schema{type: :string},
is_confirmed: %Schema{
type: :boolean,
description:
@ -164,6 +167,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
example: %{
"acct" => "foobar",
"avatar" => "https://mypleroma.com/images/avi.png",
"avatar_description" => "closeup of tuxedo cat staring directly into camera",
"avatar_static" => "https://mypleroma.com/images/avi.png",
"bot" => false,
"created_at" => "2020-03-24T13:05:58.000Z",
@ -174,6 +178,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,
@ -181,6 +186,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
"pleroma" => %{
"allow_following_move" => true,
"background_image" => nil,
"background_image_description" => "",
"is_confirmed" => false,
"hide_favorites" => true,
"hide_followers" => false,

View file

@ -200,8 +200,14 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|> Maps.put_if_present(:bio, params[:note])
|> Maps.put_if_present(:raw_bio, params[:note])
|> Maps.put_if_present(:avatar, params[:avatar], user_image_value)
|> Maps.put_if_present(:avatar_description, params[:avatar_description])
|> Maps.put_if_present(:banner, params[:header], user_image_value)
|> Maps.put_if_present(:header_description, params[:header_description])
|> Maps.put_if_present(:background, params[:pleroma_background_image], user_image_value)
|> Maps.put_if_present(
:background_description,
params[:pleroma_background_image_description]
)
|> Maps.put_if_present(
:raw_fields,
params[:fields_attributes],
@ -256,6 +262,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
with_pleroma_settings: true
)
else
# Map errors to translation string constants
{:error, %Ecto.Changeset{errors: [avatar: {"file is too large", _}]}} ->
render_error(conn, :request_entity_too_large, "File is too large")
@ -265,6 +272,62 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
{:error, %Ecto.Changeset{errors: [background: {"file is too large", _}]}} ->
render_error(conn, :request_entity_too_large, "File is too large")
{:error,
%Ecto.Changeset{errors: [{:avatar_description, {"avatar_description is too long", _}} | _]}} ->
render_error(conn, :request_entity_too_large, "Avatar description is too long")
{:error,
%Ecto.Changeset{
errors: [
{:avatar_description,
{"avatar_description needs avatar to be set before or simultaneously", _}}
| _
]
}} ->
render_error(
conn,
:unprocessable_entity,
"Avatar description needs avatar to be set before or simultaneously"
)
{:error,
%Ecto.Changeset{errors: [{:header_description, {"header_description is too long", _}} | _]}} ->
render_error(conn, :request_entity_too_large, "Banner description is too long")
{:error,
%Ecto.Changeset{
errors: [
{:header_description,
{"header_description needs banner to be set before or simultaneously", _}}
| _
]
}} ->
render_error(
conn,
:unprocessable_entity,
"Banner description needs banner to be set before or simultaneously"
)
{:error,
%Ecto.Changeset{
errors: [{:background_description, {"background description is too long", _}} | _]
}} ->
render_error(conn, :request_entity_too_large, "Background description is too long")
{:error,
%Ecto.Changeset{
errors: [
{:background_description,
{"background_description needs background to be set before or simultaneously", _}}
| _
]
}} ->
render_error(
conn,
:unprocessable_entity,
"Background description needs background to be set before or simultaneously"
)
_e ->
render_error(conn, :forbidden, "Invalid request")
end

View file

@ -216,8 +216,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 = User.image_description(user.avatar, "")
header = User.banner_url(user) |> MediaProxy.url()
header_static = User.banner_url(user) |> MediaProxy.preview_url(static: true)
header_description = User.image_description(user.banner, "")
following_count =
if !user.hide_follows_count or !user.hide_follows or opts[:for] == user,
@ -287,8 +289,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
url: user.uri || user.ap_id,
avatar: avatar,
avatar_static: avatar_static,
avatar_description: avatar_description,
header: header,
header_static: header_static,
header_description: header_description,
emojis: emojis,
fields: user.fields,
bot: bot,
@ -324,6 +328,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
relationship: relationship,
skip_thread_containment: user.skip_thread_containment,
background_image: image_url(user.background) |> MediaProxy.url(),
background_image_description: User.image_description(user.background, ""),
favicon: favicon
}
}

View file

@ -71,12 +71,14 @@ defmodule Pleroma.User.FetcherTest do
assert user.avatar == %{
"type" => "Image",
"url" => [%{"href" => "https://jk.nipponalba.scot/images/profile.jpg"}]
"url" => [%{"href" => "https://jk.nipponalba.scot/images/profile.jpg"}],
"name" => "profile picture"
}
assert user.banner == %{
"type" => "Image",
"url" => [%{"href" => "https://jk.nipponalba.scot/images/profile.jpg"}]
"url" => [%{"href" => "https://jk.nipponalba.scot/images/profile.jpg"}],
"name" => "profile picture"
}
end
@ -280,6 +282,35 @@ defmodule Pleroma.User.FetcherTest do
{:ok, ^featured_url, %{}} = Fetcher.process_featured_collection(featured_url)
end
test "fetches avatar description" do
user_id = "https://example.com/users/nicole"
user_data =
"test/fixtures/users_mock/user.json"
|> File.read!()
|> String.replace("{{nickname}}", "nicole")
|> Jason.decode!()
|> Map.delete("featured")
|> Map.update("icon", %{}, fn image -> Map.put(image, "name", "image description") end)
|> Jason.encode!()
Tesla.Mock.mock(fn
%{
method: :get,
url: ^user_id
} ->
%Tesla.Env{
status: 200,
body: user_data,
headers: [{"content-type", "application/activity+json"}]
}
end)
{:ok, user} = Fetcher.make_user_from_ap_id(user_id)
assert user.avatar["name"] == "image description"
end
describe "fetch_follow_information_for_user" do
test "synchronizes following/followers counters" do
user =

View file

@ -97,6 +97,22 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
assert result["icon"]["url"] == "https://someurl"
assert result["image"]["url"] == "https://somebanner"
assert result["backgroundUrl"]["url"] == "https://somebackground"
assert result["icon"]["name"] == ""
assert result["image"]["name"] == ""
end
test "Avatar has a description if the user set one" do
user =
insert(:user,
avatar: %{
"url" => [%{"href" => "https://someurl"}],
"name" => "a drawing of pleroma-tan using pleroma groups"
}
)
result = UserView.render("user.json", %{user: user})
assert result["icon"]["name"] == "a drawing of pleroma-tan using pleroma groups"
end
test "renders an invisible user with the invisible property set to true" do

View file

@ -399,6 +399,214 @@ 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 "does not wipe media description when uploading new image", %{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 == %{}
assert User.image_description(user.banner) == ""
desc = "many woozy akkos in stylised chibi style"
conn
|> patch("/api/v1/accounts/update_credentials", %{
"header" => new_avatar,
"header_description" => desc
})
|> json_response_and_validate_schema(200)
user = User.get_by_id(user.id)
assert User.image_description(user.banner) == desc
conn
|> assign(:user, User.get_by_id(user.id))
|> patch("/api/v1/accounts/update_credentials", %{"header" => new_avatar})
|> json_response_and_validate_schema(200)
user = User.get_by_id(user.id)
assert User.image_description(user.banner) == desc
end
test "adds background description with a new background upload", %{user: user, conn: conn} do
image = %Plug.Upload{
content_type: "image/jpeg",
path: Path.absname("test/fixtures/image.jpg"),
filename: "an_image.jpg"
}
alt = "arctus forest scenery"
res =
patch(conn, "/api/v1/accounts/update_credentials", %{
"pleroma_background_image" => image,
"pleroma_background_image_description" => alt
})
assert json_response_and_validate_schema(res, 200)
user = User.get_by_id(user.id)
assert User.image_description(user.background) == alt
end
test "updates description of existing background without image reupload", %{
user: user,
conn: conn
} do
image = %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", %{"pleroma_background_image" => image})
user = User.get_by_id(user.id)
assert User.image_description(user.background) == ""
alt1 = "shinyyyyyyy"
assert conn
|> assign(:user, User.get_by_id(user.id))
|> patch("/api/v1/accounts/update_credentials", %{
"pleroma_background_image_description" => alt1
})
|> json_response_and_validate_schema(200)
user = User.get_by_id(user.id)
assert User.image_description(user.background) == alt1
alt2 = "me and shiny chariot"
assert conn
|> assign(:user, User.get_by_id(user.id))
|> patch("/api/v1/accounts/update_credentials", %{
"pleroma_background_image_description" => alt2
})
|> json_response_and_validate_schema(200)
user = User.get_by_id(user.id)
assert User.image_description(user.background) == alt2
end
test "limits profile media alt text", %{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 = 100
clear_config([:instance, :description_limit], description_limit)
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 "refuses to set a media description without media being present or set", %{conn: conn} do
# It not being a possible is a limitation of how we store things.
# This test is here to ensure the user will be made aware of it.
assert %{"error" => "Avatar description needs avatar to be set before or simultaneously"} =
conn
|> patch("/api/v1/accounts/update_credentials", %{
"avatar_description" => "pure void"
})
|> json_response_and_validate_schema(422)
assert %{"error" => "Banner description needs banner to be set before or simultaneously"} =
conn
|> patch("/api/v1/accounts/update_credentials", %{
"header_description" => "pure void"
})
|> json_response_and_validate_schema(422)
assert %{
"error" =>
"Background description needs background to be set before or simultaneously"
} =
conn
|> patch("/api/v1/accounts/update_credentials", %{
"pleroma_background_image_description" => "pure void"
})
|> json_response_and_validate_schema(422)
end
test "allows wiping media description without media being present or set", %{conn: conn} do
conn
|> patch("/api/v1/accounts/update_credentials", %{"avatar_description" => ""})
|> json_response_and_validate_schema(200)
conn
|> patch("/api/v1/accounts/update_credentials", %{"header_description" => ""})
|> json_response_and_validate_schema(200)
conn
|> patch("/api/v1/accounts/update_credentials", %{
"pleroma_background_image_description" => ""
})
|> json_response_and_validate_schema(200)
end
test "Strip / from upload files", %{user: user, conn: conn} do
new_image = %Plug.Upload{
content_type: "image/jpeg",

View file

@ -71,8 +71,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
},
avatar: "http://localhost:4001/images/avi.png",
avatar_static: "http://localhost:4001/images/avi.png",
avatar_description: "",
header: "http://localhost:4001/images/banner.png",
header_static: "http://localhost:4001/images/banner.png",
header_description: "",
emojis: [
%{
static_url: "/file.png",
@ -98,6 +100,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
ap_id: user.ap_id,
also_known_as: ["https://shitposter.zone/users/shp"],
background_image: "https://example.com/images/asuka_hospital.png",
background_image_description: "",
favicon: nil,
is_confirmed: true,
tags: [],
@ -228,8 +231,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
url: user.ap_id,
avatar: "http://localhost:4001/images/avi.png",
avatar_static: "http://localhost:4001/images/avi.png",
avatar_description: "",
header: "http://localhost:4001/images/banner.png",
header_static: "http://localhost:4001/images/banner.png",
header_description: "",
emojis: [],
fields: [],
bot: true,
@ -257,6 +262,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
ap_id: user.ap_id,
also_known_as: [],
background_image: nil,
background_image_description: "",
favicon: "http://localhost:4001/favicon.png",
is_confirmed: true,
tags: [],