Treat known quotes and replies as such even if parent unavailable
Happens commonly for e.g. replies to follower-only posts if no one one your instance follows the replied-to account or replies/quotes of deleted posts. Before this change Masto API response would treat those replies as root posts, making it hard to automatically or mentally filter them out. With this change replies already show up sensibly as recognisable replies in akkoma-fe. Quotes of unavailable posts however still show up as if they weren’t quotes at all, but this can only be improved client-side. Fixes: #715
This commit is contained in:
parent
4744ae4328
commit
0907521971
4 changed files with 82 additions and 10 deletions
12
CHANGELOG.md
12
CHANGELOG.md
|
|
@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
|
||||
## Unreleased
|
||||
### REMOVED
|
||||
|
||||
### Added
|
||||
- status responses include two new fields for ActivityPub cross-referencing: `akkoma.quote_apid` and `akkoma.in_reply_to_apid`
|
||||
|
||||
### Fixed
|
||||
- replies and quotes to unresolvable posts now fill out IDs for replied to
|
||||
status, user or quoted status with a 404-ing ID to make them recognisable as
|
||||
replies/quotes instead of pretending they’re root posts
|
||||
|
||||
### Changed
|
||||
|
||||
|
||||
## 2025.10
|
||||
|
||||
|
|
|
|||
|
|
@ -250,6 +250,16 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
|
|||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
in_reply_to_apid: %Schema{
|
||||
type: :string,
|
||||
nullable: true,
|
||||
description: "The AcitivityPub ID this post is replying to, if it is a reply."
|
||||
},
|
||||
quote_apid: %Schema{
|
||||
type: :string,
|
||||
nullable: true,
|
||||
description: "The AcitivityPub ID this post is quoting, if it is a quote."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -26,6 +26,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
|
||||
import Pleroma.Web.ActivityPub.Visibility, only: [get_visibility: 1, visible_for_user?: 2]
|
||||
|
||||
# Used as a placeholder to represent known-existing relatives we do cannot resolve locally
|
||||
# will always 404 when supplied to API endpoints
|
||||
@ghost_flake_id "_"
|
||||
|
||||
defp fetch_rich_media_for_activities(activities) do
|
||||
Enum.each(activities, fn activity ->
|
||||
Card.get_by_activity(activity)
|
||||
|
|
@ -277,9 +281,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
nil
|
||||
end
|
||||
|
||||
reply_to = get_reply_to(activity, opts)
|
||||
reply_to_apid = get_single_apid(object.data, "inReplyTo")
|
||||
reply_to = reply_to_apid && get_reply_to(activity, opts)
|
||||
reply_to_id = reply_to_apid && get_id_or_ghost(reply_to)
|
||||
|
||||
reply_to_user = reply_to && CommonAPI.get_user(reply_to.data["actor"])
|
||||
reply_to_user_id = reply_to_apid && get_id_or_ghost(reply_to_user)
|
||||
|
||||
history_len =
|
||||
1 +
|
||||
|
|
@ -363,7 +370,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
|
||||
{pinned?, pinned_at} = pin_data(object, user)
|
||||
|
||||
quote = Activity.get_quoted_activity_from_object(object)
|
||||
quote_apid = get_single_apid(object.data, "quoteUri")
|
||||
quote = quote_apid && Activity.get_quoted_activity_from_object(object)
|
||||
quote_id = quote_apid && get_id_or_ghost(quote)
|
||||
|
||||
lang = language(object)
|
||||
|
||||
%{
|
||||
|
|
@ -375,8 +385,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
user: user,
|
||||
for: opts[:for]
|
||||
}),
|
||||
in_reply_to_id: reply_to && to_string(reply_to.id),
|
||||
in_reply_to_account_id: reply_to_user && to_string(reply_to_user.id),
|
||||
in_reply_to_id: reply_to_id,
|
||||
in_reply_to_account_id: reply_to_user_id,
|
||||
reblog: nil,
|
||||
card: card,
|
||||
content: content_html,
|
||||
|
|
@ -401,7 +411,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
application: build_application(object.data["generator"]),
|
||||
language: lang,
|
||||
emojis: build_emojis(object.data["emoji"]),
|
||||
quote_id: if(quote, do: quote.id, else: nil),
|
||||
quote_id: quote_id,
|
||||
quote: maybe_render_quote(quote, opts),
|
||||
emoji_reactions: emoji_reactions,
|
||||
pleroma: %{
|
||||
|
|
@ -419,7 +429,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
pinned_at: pinned_at
|
||||
},
|
||||
akkoma: %{
|
||||
source: object.data["source"]
|
||||
source: object.data["source"],
|
||||
# Note: these AP IDs will also be filled out if we cannot resolve the actual object
|
||||
# (e.g. because it’s a private post we aren't allowed to access, or just federation woes)
|
||||
# allowing users to potentially discover the full context from other accounts/servers.
|
||||
in_reply_to_apid: reply_to_apid,
|
||||
quote_apid: quote_apid
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -637,6 +652,26 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
end
|
||||
end
|
||||
|
||||
defp get_id_or_ghost(object) do
|
||||
(object && to_string(object.id)) || @ghost_flake_id
|
||||
end
|
||||
|
||||
defp get_single_apid(object, key) do
|
||||
apid = object[key]
|
||||
|
||||
apid =
|
||||
case apid do
|
||||
[head | _] -> head
|
||||
_ -> apid
|
||||
end
|
||||
|
||||
if apid != "" and is_binary(apid) do
|
||||
apid
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def get_reply_to(activity, %{replied_to_activities: replied_to_activities}) do
|
||||
object = Object.normalize(activity, fetch: false)
|
||||
|
||||
|
|
|
|||
|
|
@ -326,7 +326,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
|
|||
pinned_at: nil
|
||||
},
|
||||
akkoma: %{
|
||||
source: HTML.filter_tags(object_data["content"])
|
||||
source: HTML.filter_tags(object_data["content"]),
|
||||
in_reply_to_apid: nil,
|
||||
quote_apid: nil
|
||||
},
|
||||
quote_id: nil,
|
||||
quote: nil
|
||||
|
|
@ -417,6 +419,17 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
|
|||
assert status.in_reply_to_id == to_string(note.id)
|
||||
end
|
||||
|
||||
test "a reply to an unavailable post" do
|
||||
note = insert(:note, data: %{"inReplyTo" => "https://example.org/404"})
|
||||
activity = insert(:note_activity, note: note)
|
||||
|
||||
status = StatusView.render("show.json", %{activity: activity})
|
||||
|
||||
assert status.in_reply_to_id == "_"
|
||||
assert status.in_reply_to_account_id == "_"
|
||||
assert status.akkoma.in_reply_to_apid == "https://example.org/404"
|
||||
end
|
||||
|
||||
test "a quote" do
|
||||
note = insert(:note_activity)
|
||||
user = insert(:user)
|
||||
|
|
@ -433,12 +446,14 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
|
|||
end
|
||||
|
||||
test "a quote that we can't resolve" do
|
||||
note = insert(:note_activity, quoteUri: "oopsie")
|
||||
note = insert(:note, data: %{"quoteUri" => "oopsie"})
|
||||
activity = insert(:note_activity, note: note)
|
||||
|
||||
status = StatusView.render("show.json", %{activity: note})
|
||||
status = StatusView.render("show.json", %{activity: activity})
|
||||
|
||||
assert is_nil(status.quote_id)
|
||||
assert is_nil(status.quote)
|
||||
assert status.quote_id == "_"
|
||||
assert status.akkoma.quote_apid == "oopsie"
|
||||
end
|
||||
|
||||
test "a quote from a user we block" do
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue