feat(api): add the Mastodon quote entity to statuses

Mastodon 4.4+ clients read quotes from a top-level quote object with
state and quoted_status; Pleroma only had pleroma.quote, so they did
not show quote posts. Quoted statuses get a shallow quote with only
quoted_status_id, as quotes are rendered one level deep.

Inspired by Akkoma 5b7209980, which can not use the shallow form because
Akkoma already had a top-level quote status.
This commit is contained in:
Lain Soykaf 2026-10-28 11:38:49 +04:00
commit 3890fbc127
No known key found for this signature in database
4 changed files with 107 additions and 2 deletions

View file

@ -234,6 +234,29 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
type: :integer,
description: "How many statuses quoted this status."
},
quote: %Schema{
type: :object,
nullable: true,
description:
"The status being quoted, if any (Mastodon 4.4+). Quoted statuses only have `quoted_status_id`.",
properties: %{
state: %Schema{
type: :string,
enum: ["accepted", "unauthorized", "deleted"],
description: "Whether the quoted status can be shown"
},
quoted_status: %Schema{
allOf: [%OpenApiSpex.Reference{"$ref": "#/components/schemas/Status"}],
nullable: true,
description: "The quoted status, if it can be shown"
},
quoted_status_id: %Schema{
type: :string,
nullable: true,
description: "ID of the quoted status, if it can be shown"
}
}
},
reblog: %Schema{
allOf: [%OpenApiSpex.Reference{"$ref": "#/components/schemas/Status"}],
nullable: true,

View file

@ -455,6 +455,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
language: get_language(object),
emojis: build_emojis(object.data["emoji"]),
quotes_count: object.data["quotesCount"] || 0,
quote: build_quote(object, quote_activity, quote_post, opts),
pleroma: %{
local: activity.local,
conversation_id: get_context_id(activity),
@ -691,7 +692,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
%{ancestors: ancestors, descendants: descendants} =
activities
|> Enum.reverse()
|> Enum.group_by(fn %{id: id} -> if id < activity.id, do: :ancestors, else: :descendants end)
|> Enum.group_by(fn %{id: id} ->
if id < activity.id, do: :ancestors, else: :descendants
end)
|> Map.put_new(:ancestors, [])
|> Map.put_new(:descendants, [])
@ -742,6 +745,26 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
end
end
# Mastodon (4.4+) quote entity. Quoted statuses get a shallow quote that only
# has the id, as quotes are only rendered one level deep.
defp build_quote(%Object{data: %{"quoteUrl" => url}}, quote_activity, quote_post, opts)
when is_binary(url) and url != "" do
state =
cond do
is_nil(quote_activity) -> "deleted"
visible_for_user?(quote_activity, opts[:for]) -> "accepted"
true -> "unauthorized"
end
if opts[:show_quote] == false do
%{state: state, quoted_status_id: if(state == "accepted", do: to_string(quote_activity.id))}
else
%{state: state, quoted_status: if(state == "accepted", do: quote_post)}
end
end
defp build_quote(_object, _quote_activity, _quote_post, _opts), do: nil
def get_quote(activity, %{quoted_activities: quoted_activities}) do
object = Object.normalize(activity, fetch: false)