Merge pull request 'Treat known quotes and replies as such even if parent unavailable' (#991) from Oneric/akkoma:replies-unknown into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma/pulls/991
This commit is contained in:
commit
300302d432
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 @@ def render("show.json", %{activity: %{id: id, data: %{"object" => _object}} = ac
|
|||
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 @@ def render("show.json", %{activity: %{id: id, data: %{"object" => _object}} = ac
|
|||
|
||||
{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 @@ def render("show.json", %{activity: %{id: id, data: %{"object" => _object}} = ac
|
|||
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 @@ def render("show.json", %{activity: %{id: id, data: %{"object" => _object}} = ac
|
|||
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 @@ def render("show.json", %{activity: %{id: id, data: %{"object" => _object}} = ac
|
|||
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 @@ defp proxied_url(url, page_url_data) 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 @@ test "a note activity" 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 @@ test "a reply" 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 @@ test "a quote" 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…
Reference in a new issue