Fix paged featured collection fetches

This commit is contained in:
Lain Soykaf 2026-05-22 22:11:37 +04:00
commit b054c2aa42
No known key found for this signature in database
3 changed files with 168 additions and 2 deletions

View file

@ -0,0 +1 @@
Support fetching featured collection items from the first collection page

View file

@ -1881,11 +1881,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
end
@featured_collection_types ["OrderedCollection", "Collection"]
@featured_collection_page_types ["OrderedCollectionPage", "CollectionPage"]
@featured_collection_item_types @featured_collection_types ++ @featured_collection_page_types
def pin_data_from_featured_collection(%{
"type" => type,
"orderedItems" => objects
})
when type in ["OrderedCollection", "Collection"] do
when type in @featured_collection_item_types do
Map.new(objects, fn
%{"id" => object_ap_id} -> {object_ap_id, NaiveDateTime.utc_now()}
object_ap_id when is_binary(object_ap_id) -> {object_ap_id, NaiveDateTime.utc_now()}
@ -1903,7 +1907,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
def fetch_and_prepare_featured_from_ap_id(ap_id) do
with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id) do
{:ok, pin_data_from_featured_collection(data)}
{:ok, prepare_featured_collection(data)}
else
e ->
Logger.error("Could not decode featured collection at fetch #{ap_id}, #{inspect(e)}")
@ -1911,6 +1915,34 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
end
defp prepare_featured_collection(%{"orderedItems" => objects} = data) when is_list(objects) do
pin_data_from_featured_collection(data)
end
defp prepare_featured_collection(%{
"type" => type,
"first" => %{"type" => page_type} = first
})
when type in @featured_collection_types and page_type in @featured_collection_page_types do
pin_data_from_featured_collection(first)
end
defp prepare_featured_collection(%{"type" => type, "first" => first})
when type in @featured_collection_types and is_binary(first) do
case Fetcher.fetch_and_contain_remote_object_from_id(first) do
{:ok, data} ->
pin_data_from_featured_collection(data)
e ->
Logger.error("Could not decode featured collection page at fetch #{first}, #{inspect(e)}")
%{}
end
end
defp prepare_featured_collection(data) do
pin_data_from_featured_collection(data)
end
def enqueue_pin_fetches(%{pinned_objects: pins}) do
# enqueue a task to fetch all pinned objects
Enum.each(pins, fn {ap_id, _} ->

View file

@ -415,6 +415,105 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
assert %{data: %{"id" => ^object_url}} = Object.get_by_ap_id(object_url)
end
test "fetches user featured collection from the first collection page" do
ap_id = "https://example.com/users/lain"
featured_url = "https://example.com/users/lain/collections/featured"
first_page_url = "#{featured_url}?page=true"
user_data =
"test/fixtures/users_mock/user.json"
|> File.read!()
|> String.replace("{{nickname}}", "lain")
|> Jason.decode!()
|> Map.put("featured", featured_url)
|> Jason.encode!()
object_id = Ecto.UUID.generate()
object_url = "https://example.com/objects/#{object_id}"
featured_data =
Jason.encode!(%{
"id" => featured_url,
"type" => "OrderedCollection",
"first" => first_page_url
})
first_page_data =
Jason.encode!(%{
"id" => first_page_url,
"type" => "OrderedCollectionPage",
"partOf" => featured_url,
"orderedItems" => [object_url]
})
object_data =
"test/fixtures/statuses/note.json"
|> File.read!()
|> String.replace("{{object_id}}", object_id)
|> String.replace("{{nickname}}", "lain")
Tesla.Mock.mock(fn
%{
method: :get,
url: ^ap_id
} ->
%Tesla.Env{
status: 200,
body: user_data,
headers: [{"content-type", "application/activity+json"}]
}
%{
method: :get,
url: ^featured_url
} ->
%Tesla.Env{
status: 200,
body: featured_data,
headers: [{"content-type", "application/activity+json"}]
}
%{
method: :get,
url: ^first_page_url
} ->
%Tesla.Env{
status: 200,
body: first_page_data,
headers: [{"content-type", "application/activity+json"}]
}
%{
method: :get,
url: ^object_url
} ->
%Tesla.Env{
status: 200,
body: object_data,
headers: [{"content-type", "application/activity+json"}]
}
end)
refute capture_log(fn ->
{:ok, user} = ActivityPub.make_user_from_ap_id(ap_id)
assert_enqueued(
worker: Pleroma.Workers.RemoteFetcherWorker,
args: %{
"op" => "fetch_remote",
"id" => object_url,
"depth" => 1
}
)
Pleroma.Tests.ObanHelpers.perform_all()
assert user.featured_address == featured_url
assert Map.has_key?(user.pinned_objects, object_url)
assert %{data: %{"id" => ^object_url}} = Object.get_by_ap_id(object_url)
end) =~ "Could not parse featured collection"
end
test "fetches user birthday information from misskey" do
user_id = "https://misskey.io/@mkljczk"
@ -2818,4 +2917,38 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
"first" => "https://social.example/users/alice/collections/featured?page=true"
})
end
test "fetch_and_prepare_featured_from_ap_id handles embedded first collection pages" do
featured_url = "https://social.example/users/alice/collections/featured"
first_page_url = "#{featured_url}?page=true"
object_url = "https://social.example/objects/1"
featured_data =
Jason.encode!(%{
"id" => featured_url,
"type" => "OrderedCollection",
"first" => %{
"id" => first_page_url,
"type" => "OrderedCollectionPage",
"partOf" => featured_url,
"orderedItems" => [object_url]
}
})
Tesla.Mock.mock(fn
%{method: :get, url: ^featured_url} ->
%Tesla.Env{
status: 200,
body: featured_data,
headers: [{"content-type", "application/activity+json"}]
}
end)
refute capture_log(fn ->
assert {:ok, pinned_objects} =
ActivityPub.fetch_and_prepare_featured_from_ap_id(featured_url)
assert Map.has_key?(pinned_objects, object_url)
end) =~ "Could not parse featured collection"
end
end