AP ingest: normalize inReplyTo shapes
This commit is contained in:
parent
6701f61990
commit
bff67650e1
8 changed files with 245 additions and 12 deletions
|
|
@ -41,22 +41,34 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do
|
|||
def fix_object_defaults(data) do
|
||||
data = Maps.filter_empty_values(data)
|
||||
|
||||
in_reply_to_id = in_reply_to_id(data["inReplyTo"])
|
||||
|
||||
context =
|
||||
Utils.maybe_create_context(
|
||||
data["context"] || data["conversation"] || data["inReplyTo"] || data["id"]
|
||||
data["context"] || data["conversation"] || in_reply_to_id || data["id"]
|
||||
)
|
||||
|
||||
%User{follower_address: follower_collection} = User.get_cached_by_ap_id(data["attributedTo"])
|
||||
data = Map.put(data, "context", context)
|
||||
|
||||
data
|
||||
|> Map.put("context", context)
|
||||
|> cast_and_filter_recipients("to", follower_collection)
|
||||
|> cast_and_filter_recipients("cc", follower_collection)
|
||||
|> cast_and_filter_recipients("bto", follower_collection)
|
||||
|> cast_and_filter_recipients("bcc", follower_collection)
|
||||
|> Transmogrifier.fix_implicit_addressing(follower_collection)
|
||||
with attributed_to when is_binary(attributed_to) <- data["attributedTo"],
|
||||
%User{follower_address: follower_collection} <- User.get_cached_by_ap_id(attributed_to) do
|
||||
data
|
||||
|> cast_and_filter_recipients("to", follower_collection)
|
||||
|> cast_and_filter_recipients("cc", follower_collection)
|
||||
|> cast_and_filter_recipients("bto", follower_collection)
|
||||
|> cast_and_filter_recipients("bcc", follower_collection)
|
||||
|> Transmogrifier.fix_implicit_addressing(follower_collection)
|
||||
else
|
||||
_ -> data
|
||||
end
|
||||
end
|
||||
|
||||
defp in_reply_to_id(in_reply_to) when is_binary(in_reply_to), do: in_reply_to
|
||||
defp in_reply_to_id(%{"id" => in_reply_to}) when is_binary(in_reply_to), do: in_reply_to
|
||||
defp in_reply_to_id(%{"href" => in_reply_to}) when is_binary(in_reply_to), do: in_reply_to
|
||||
defp in_reply_to_id([in_reply_to | _]), do: in_reply_to_id(in_reply_to)
|
||||
defp in_reply_to_id(_), do: nil
|
||||
|
||||
def fix_activity_addressing(activity) do
|
||||
%User{follower_address: follower_collection} = User.get_cached_by_ap_id(activity["actor"])
|
||||
|
||||
|
|
|
|||
|
|
@ -201,9 +201,29 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options)
|
||||
when not is_nil(in_reply_to) do
|
||||
in_reply_to_id = prepare_in_reply_to(in_reply_to)
|
||||
|
||||
object =
|
||||
cond do
|
||||
is_list(in_reply_to) ->
|
||||
if not_empty_string(in_reply_to_id) do
|
||||
Map.put(object, "inReplyTo", in_reply_to_id)
|
||||
else
|
||||
Map.delete(object, "inReplyTo")
|
||||
end
|
||||
|
||||
is_map(in_reply_to) and is_nil(in_reply_to["id"]) and is_binary(in_reply_to["href"]) ->
|
||||
Map.put(object, "inReplyTo", in_reply_to["href"])
|
||||
|
||||
is_map(in_reply_to) and is_nil(in_reply_to["id"]) and is_nil(in_reply_to["href"]) ->
|
||||
Map.delete(object, "inReplyTo")
|
||||
|
||||
true ->
|
||||
object
|
||||
end
|
||||
|
||||
depth = (options[:depth] || 0) + 1
|
||||
|
||||
if Federator.allowed_thread_distance?(depth) do
|
||||
if not_empty_string(in_reply_to_id) and Federator.allowed_thread_distance?(depth) do
|
||||
with {:ok, replied_object} <- get_obj_helper(in_reply_to_id, options),
|
||||
%Activity{} <- Activity.get_create_by_object_ap_id(replied_object.data["id"]) do
|
||||
object
|
||||
|
|
@ -249,9 +269,17 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
is_map(in_reply_to) && is_bitstring(in_reply_to["id"]) ->
|
||||
in_reply_to["id"]
|
||||
|
||||
is_map(in_reply_to) && is_bitstring(in_reply_to["href"]) ->
|
||||
in_reply_to["href"]
|
||||
|
||||
is_list(in_reply_to) && is_bitstring(Enum.at(in_reply_to, 0)) ->
|
||||
Enum.at(in_reply_to, 0)
|
||||
|
||||
is_list(in_reply_to) && is_map(Enum.at(in_reply_to, 0)) ->
|
||||
in_reply_to
|
||||
|> Enum.at(0)
|
||||
|> prepare_in_reply_to()
|
||||
|
||||
true ->
|
||||
""
|
||||
end
|
||||
|
|
|
|||
30
test/fixtures/fedidev.fun/in_reply_to_collection.json
vendored
Normal file
30
test/fixtures/fedidev.fun/in_reply_to_collection.json
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams"
|
||||
],
|
||||
"type": "Create",
|
||||
"id": "https://fedidev.fun/activity/in-reply-to-collection",
|
||||
"actor": "https://fedidev.fun/users/alice",
|
||||
"to": [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"cc": [],
|
||||
"published": "2025-11-21T14:40:08Z",
|
||||
"object": {
|
||||
"type": "Note",
|
||||
"id": "https://fedidev.fun/objects/note-in-reply-to-collection",
|
||||
"attributedTo": "https://fedidev.fun/users/alice",
|
||||
"to": [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"cc": [],
|
||||
"published": "2025-11-21T14:40:08Z",
|
||||
"content": "collection",
|
||||
"inReplyTo": {
|
||||
"type": "Collection",
|
||||
"items": [
|
||||
"https://fedidev.fun/users/alice"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
35
test/fixtures/fedidev.fun/in_reply_to_embedded_note.json
vendored
Normal file
35
test/fixtures/fedidev.fun/in_reply_to_embedded_note.json
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams"
|
||||
],
|
||||
"type": "Create",
|
||||
"id": "https://fedidev.fun/activity/in-reply-to-embedded-note",
|
||||
"actor": "https://fedidev.fun/users/alice",
|
||||
"to": [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"cc": [],
|
||||
"published": "2025-11-21T14:40:21Z",
|
||||
"object": {
|
||||
"type": "Note",
|
||||
"id": "https://fedidev.fun/objects/note-in-reply-to-embedded-note",
|
||||
"attributedTo": "https://fedidev.fun/users/alice",
|
||||
"to": [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"cc": [],
|
||||
"published": "2025-11-21T14:40:21Z",
|
||||
"content": "embedded note",
|
||||
"inReplyTo": {
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
"type": "Note",
|
||||
"id": "https://fedidev.fun/assets/note1.jsonap",
|
||||
"to": [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"attributedTo": "https://fedidev.fun/users/alice",
|
||||
"content": "One",
|
||||
"published": "2024-01-06T13:11:45Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
28
test/fixtures/fedidev.fun/in_reply_to_link.json
vendored
Normal file
28
test/fixtures/fedidev.fun/in_reply_to_link.json
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams"
|
||||
],
|
||||
"type": "Create",
|
||||
"id": "https://fedidev.fun/activity/in-reply-to-link",
|
||||
"actor": "https://fedidev.fun/users/alice",
|
||||
"to": [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"cc": [],
|
||||
"published": "2025-11-21T14:39:56Z",
|
||||
"object": {
|
||||
"type": "Note",
|
||||
"id": "https://fedidev.fun/objects/note-in-reply-to-link",
|
||||
"attributedTo": "https://fedidev.fun/users/alice",
|
||||
"to": [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"cc": [],
|
||||
"published": "2025-11-21T14:39:56Z",
|
||||
"content": "Link in inReplyTo",
|
||||
"inReplyTo": {
|
||||
"type": "Link",
|
||||
"href": "https://fedidev.fun/assets/note1.jsonap"
|
||||
}
|
||||
}
|
||||
}
|
||||
28
test/fixtures/fedidev.fun/in_reply_to_list.json
vendored
Normal file
28
test/fixtures/fedidev.fun/in_reply_to_list.json
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams"
|
||||
],
|
||||
"type": "Create",
|
||||
"id": "https://fedidev.fun/activity/in-reply-to-list",
|
||||
"actor": "https://fedidev.fun/users/alice",
|
||||
"to": [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"cc": [],
|
||||
"published": "2025-11-21T14:40:08Z",
|
||||
"object": {
|
||||
"type": "Note",
|
||||
"id": "https://fedidev.fun/objects/note-in-reply-to-list",
|
||||
"attributedTo": "https://fedidev.fun/users/alice",
|
||||
"to": [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"cc": [],
|
||||
"published": "2025-11-21T14:40:08Z",
|
||||
"content": "two elements in inReplyTo",
|
||||
"inReplyTo": [
|
||||
"https://fedidev.fun/assets/note1.jsonap",
|
||||
"https://fedidev.fun/assets/note2.jsonap"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.Transmogrifier.FedidevFunInReplyToTest do
|
||||
use Oban.Testing, repo: Pleroma.Repo
|
||||
use Pleroma.DataCase
|
||||
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
|
||||
import Pleroma.Factory
|
||||
|
||||
setup_all do
|
||||
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
||||
:ok
|
||||
end
|
||||
|
||||
defp ensure_remote_actor!(ap_id) when is_binary(ap_id) do
|
||||
case User.get_cached_by_ap_id(ap_id) do
|
||||
%User{} ->
|
||||
:ok
|
||||
|
||||
_ ->
|
||||
insert(:user,
|
||||
local: false,
|
||||
ap_id: ap_id,
|
||||
follower_address: ap_id <> "/followers",
|
||||
following_address: ap_id <> "/following",
|
||||
featured_address: ap_id <> "/collections/featured",
|
||||
inbox: ap_id <> "/inbox"
|
||||
)
|
||||
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defp ingest_fixture!(rel_path) do
|
||||
data = rel_path |> File.read!() |> Jason.decode!()
|
||||
ensure_remote_actor!(data["actor"])
|
||||
|
||||
assert {:ok, activity} = Transmogrifier.handle_incoming(data)
|
||||
object = Object.normalize(activity.data["object"], fetch: false)
|
||||
|
||||
{activity, object}
|
||||
end
|
||||
|
||||
test "normalizes Link inReplyTo to a string" do
|
||||
{_activity, object} = ingest_fixture!("test/fixtures/fedidev.fun/in_reply_to_link.json")
|
||||
assert object.data["inReplyTo"] == "https://fedidev.fun/assets/note1.jsonap"
|
||||
end
|
||||
|
||||
test "normalizes list inReplyTo to the first object ID" do
|
||||
{_activity, object} = ingest_fixture!("test/fixtures/fedidev.fun/in_reply_to_list.json")
|
||||
assert object.data["inReplyTo"] == "https://fedidev.fun/assets/note1.jsonap"
|
||||
end
|
||||
|
||||
test "drops invalid map shapes in inReplyTo" do
|
||||
{_activity, object} =
|
||||
ingest_fixture!("test/fixtures/fedidev.fun/in_reply_to_collection.json")
|
||||
|
||||
assert object.data["inReplyTo"] == nil
|
||||
end
|
||||
|
||||
test "accepts embedded objects in inReplyTo" do
|
||||
{_activity, object} =
|
||||
ingest_fixture!("test/fixtures/fedidev.fun/in_reply_to_embedded_note.json")
|
||||
|
||||
assert object.data["inReplyTo"] == "https://fedidev.fun/assets/note1.jsonap"
|
||||
end
|
||||
end
|
||||
|
|
@ -572,11 +572,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.NoteHandlingTest do
|
|||
Map.put(data["object"], "inReplyTo", ["https://shitposter.club/notice/2827873"])
|
||||
|
||||
modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
|
||||
assert modified_object["inReplyTo"] == ["https://shitposter.club/notice/2827873"]
|
||||
assert modified_object["inReplyTo"] == "https://shitposter.club/notice/2827873"
|
||||
|
||||
object_with_reply = Map.put(data["object"], "inReplyTo", [])
|
||||
modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
|
||||
assert modified_object["inReplyTo"] == []
|
||||
assert modified_object["inReplyTo"] == nil
|
||||
end
|
||||
|
||||
test "returns modified object when allowed incoming reply", %{data: data} do
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue