view/nodeinfo: use string keys

This makes embedded nodeinfo data
consistent between local and remote users
This commit is contained in:
Oneric 2026-02-07 00:00:00 +00:00
commit a454af32f5
5 changed files with 71 additions and 68 deletions

View file

@ -184,8 +184,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
nodeinfo =
if Pleroma.Config.get!([:instance, :filter_embedded_nodeinfo]) and instance.nodeinfo do
%{}
|> maybe_put_nodeinfo(instance.nodeinfo, :version)
|> maybe_put_nodeinfo(instance.nodeinfo, :software)
|> maybe_put_nodeinfo(instance.nodeinfo, "version")
|> maybe_put_nodeinfo(instance.nodeinfo, "software")
else
instance.nodeinfo
end
@ -452,8 +452,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
defp maybe_put_email_address(data, _, _), do: data
defp maybe_put_nodeinfo(map, nodeinfo, key) do
# local nodeinfo uses atom keys, all remote instances string keys
val = nodeinfo[key] || nodeinfo[to_string(key)]
val = nodeinfo[key]
if val do
Map.put(map, key, val)

View file

@ -27,65 +27,69 @@ defmodule Pleroma.Web.Nodeinfo.Nodeinfo do
federation = InstanceView.federation()
features = InstanceView.features()
# (Unlike most of our views)
# This uses string keys for consistency with remote nodeinfo data
%{
version: "2.0",
software: %{
name: Pleroma.Application.name() |> String.downcase(),
version: Pleroma.Application.version()
"version" => "2.0",
"software" => %{
"name" => Pleroma.Application.name() |> String.downcase(),
"version" => Pleroma.Application.version()
},
protocols: Publisher.gather_nodeinfo_protocol_names(),
services: %{
inbound: [],
outbound: []
"protocols" => Publisher.gather_nodeinfo_protocol_names(),
"services" => %{
"inbound" => [],
"outbound" => []
},
openRegistrations: Config.get([:instance, :registrations_open]),
usage: %{
users: %{
total: Map.get(stats, :user_count, 0),
activeMonth: Pleroma.User.active_user_count(30),
activeHalfyear: Pleroma.User.active_user_count(180)
"openRegistrations" => Config.get([:instance, :registrations_open]),
"usage" => %{
"users" => %{
"total" => Map.get(stats, :user_count, 0),
"activeMonth" => Pleroma.User.active_user_count(30),
"activeHalfyear" => Pleroma.User.active_user_count(180)
},
localPosts: Map.get(stats, :status_count, 0)
"localPosts" => Map.get(stats, :status_count, 0)
},
metadata: %{
nodeName: Config.get([:instance, :name]),
nodeDescription: description(),
private: !Config.get([:instance, :public], true),
suggestions: %{
enabled: false
"metadata" => %{
"nodeName" => Config.get([:instance, :name]),
"nodeDescription" => description(),
"private" => !Config.get([:instance, :public], true),
"suggestions" => %{
"enabled" => false
},
staffAccounts: staff_accounts,
federation: federation,
pollLimits: Config.get([:instance, :poll_limits]),
postFormats: Config.get([:instance, :allowed_post_formats]),
uploadLimits: %{
general: Config.get([:instance, :upload_limit]),
avatar: Config.get([:instance, :avatar_upload_limit]),
banner: Config.get([:instance, :banner_upload_limit]),
background: Config.get([:instance, :background_upload_limit])
"staffAccounts" => staff_accounts,
"federation" => federation,
"pollLimits" => Config.get([:instance, :poll_limits]),
"postFormats" => Config.get([:instance, :allowed_post_formats]),
"uploadLimits" => %{
"general" => Config.get([:instance, :upload_limit]),
"avatar" => Config.get([:instance, :avatar_upload_limit]),
"banner" => Config.get([:instance, :banner_upload_limit]),
"background" => Config.get([:instance, :background_upload_limit])
},
fieldsLimits: %{
maxFields: Config.get([:instance, :max_account_fields]),
maxRemoteFields: Config.get([:instance, :max_remote_account_fields]),
nameLength: Config.get([:instance, :account_field_name_length]),
valueLength: Config.get([:instance, :account_field_value_length])
"fieldsLimits" => %{
"maxFields" => Config.get([:instance, :max_account_fields]),
"maxRemoteFields" => Config.get([:instance, :max_remote_account_fields]),
"nameLength" => Config.get([:instance, :account_field_name_length]),
"valueLength" => Config.get([:instance, :account_field_value_length])
},
accountActivationRequired: Config.get([:instance, :account_activation_required], false),
invitesEnabled: Config.get([:instance, :invites_enabled], false),
mailerEnabled: Config.get([Pleroma.Emails.Mailer, :enabled], false),
features: features,
restrictedNicknames: Config.get([Pleroma.User, :restricted_nicknames]),
skipThreadContainment: Config.get([:instance, :skip_thread_containment], false),
privilegedStaff: Config.get([:instance, :privileged_staff]),
localBubbleInstances: Config.get([:instance, :local_bubble], []),
publicTimelineVisibility: %{
federated:
"accountActivationRequired" =>
Config.get([:instance, :account_activation_required], false),
"invitesEnabled" => Config.get([:instance, :invites_enabled], false),
"mailerEnabled" => Config.get([Pleroma.Emails.Mailer, :enabled], false),
"features" => features,
"restrictedNicknames" => Config.get([Pleroma.User, :restricted_nicknames]),
"skipThreadContainment" => Config.get([:instance, :skip_thread_containment], false),
"privilegedStaff" => Config.get([:instance, :privileged_staff]),
"localBubbleInstances" => Config.get([:instance, :local_bubble], []),
"publicTimelineVisibility" => %{
"federated" =>
!Config.restrict_unauthenticated_access?(:timelines, :federated) &&
Config.get([:instance, :federated_timeline_available], true),
local: !Config.restrict_unauthenticated_access?(:timelines, :local),
bubble: !Config.restrict_unauthenticated_access?(:timelines, :bubble)
"local" => !Config.restrict_unauthenticated_access?(:timelines, :local),
"bubble" => !Config.restrict_unauthenticated_access?(:timelines, :bubble)
},
federatedTimelineAvailable: Config.get([:instance, :federated_timeline_available], true)
"federatedTimelineAvailable" =>
Config.get([:instance, :federated_timeline_available], true)
}
}
end
@ -95,12 +99,12 @@ defmodule Pleroma.Web.Nodeinfo.Nodeinfo do
updated_software =
raw_response
|> Map.get(:software)
|> Map.put(:repository, Pleroma.Application.repository())
|> Map.get("software")
|> Map.put("repository", Pleroma.Application.repository())
raw_response
|> Map.put(:software, updated_software)
|> Map.put(:version, "2.1")
|> Map.put("software", updated_software)
|> Map.put("version", "2.1")
end
def get_nodeinfo(_version) do

View file

@ -44,7 +44,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
last_status_at: ~N[2023-12-31T15:06:17]
})
insert(:instance, %{host: "example.com", nodeinfo: %{version: "2.1"}})
insert(:instance, %{host: "example.com", nodeinfo: %{"version" => "2.1"}})
expected = %{
id: to_string(user.id),
@ -62,7 +62,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
instance: %{
name: "example.com",
nodeinfo: %{
version: "2.1"
"version" => "2.1"
},
favicon: nil
},
@ -135,7 +135,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
instance: %{
name: "somewhere.example.com",
nodeinfo: %{
version: "2.0"
"version" => "2.0"
},
favicon: "https://example.com/favicon.ico"
}
@ -151,8 +151,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
instance: %{
name: "localhost",
nodeinfo: %{
software: %{
name: "akkoma"
"software" => %{
"name" => "akkoma"
}
}
}
@ -248,7 +248,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
instance: %{
name: "localhost",
favicon: "http://localhost:4001/favicon.png",
nodeinfo: %{version: "2.0"}
nodeinfo: %{"version" => "2.0"}
},
status_ttl_days: nil,
permit_followback: false
@ -275,7 +275,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
with_mock(
Pleroma.Web.Nodeinfo.Nodeinfo,
get_nodeinfo: fn _ -> %{version: "2.0"} end
get_nodeinfo: fn _ -> %{"version" => "2.0"} end
) do
assert expected ==
AccountView.render("show.json", %{user: user, skip_visibility_check: true})

View file

@ -38,12 +38,12 @@ defmodule Pleroma.Web.Preload.Providers.InstanceTest do
test "it renders the node_info", %{"/nodeinfo/2.0.json" => nodeinfo} do
%{
metadata: metadata,
version: "2.0"
"metadata" => metadata,
"version" => "2.0"
} = nodeinfo
assert metadata.private == false
assert metadata.suggestions == %{enabled: false}
assert metadata["private"] == false
assert metadata["suggestions"] == %{"enabled" => false}
end
test "it renders the frontend configurations", %{

View file

@ -47,7 +47,7 @@ defmodule Pleroma.Factory do
def instance_factory(attrs \\ %{}) do
%Pleroma.Instances.Instance{
host: attrs[:domain] || "example.com",
nodeinfo: %{version: "2.0", openRegistrations: true},
nodeinfo: %{"version" => "2.0", "openRegistrations" => true},
unreachable_since: nil
}
|> Map.merge(attrs)