Merge branch 'develop' into mastodon-admin-api
This commit is contained in:
commit
a5aad47cff
39 changed files with 1782 additions and 103 deletions
1
changelog.d/activitypub-bot-rfc9421-fallback.fix
Normal file
1
changelog.d/activitypub-bot-rfc9421-fallback.fix
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Reject unsupported or ambiguous inbox signatures instead of queueing a doomed failed-signature retry, allowing clients to fall back to legacy HTTP Signatures.
|
||||||
1
changelog.d/fedidev-federation-compat.add
Normal file
1
changelog.d/fedidev-federation-compat.add
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Accept more real-world ActivityPub object shapes collected by fedidev.fun
|
||||||
|
|
@ -15,12 +15,28 @@ defmodule Pleroma.Object.Containment do
|
||||||
actor
|
actor
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_actor(%{"actor" => nil, "attributedTo" => actor}) when not is_nil(actor) do
|
||||||
|
get_actor(%{"actor" => actor})
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_actor(%{"actor" => nil}), do: nil
|
||||||
|
|
||||||
|
def get_actor(%{"actor" => []}), do: nil
|
||||||
|
|
||||||
def get_actor(%{"actor" => actor}) when is_list(actor) do
|
def get_actor(%{"actor" => actor}) when is_list(actor) do
|
||||||
if is_binary(Enum.at(actor, 0)) do
|
case actor do
|
||||||
Enum.at(actor, 0)
|
[first | _] when is_binary(first) ->
|
||||||
else
|
first
|
||||||
Enum.find(actor, fn %{"type" => type} -> type in ["Person", "Service", "Application"] end)
|
|
||||||
|> Map.get("id")
|
_ ->
|
||||||
|
Enum.find_value(actor, fn
|
||||||
|
%{"type" => type, "id" => id}
|
||||||
|
when type in ["Person", "Service", "Application"] and is_binary(id) ->
|
||||||
|
id
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
nil
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -28,10 +44,6 @@ defmodule Pleroma.Object.Containment do
|
||||||
id
|
id
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_actor(%{"actor" => nil, "attributedTo" => actor}) when not is_nil(actor) do
|
|
||||||
get_actor(%{"actor" => actor})
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_object(%{"object" => id}) when is_binary(id) do
|
def get_object(%{"object" => id}) when is_binary(id) do
|
||||||
id
|
id
|
||||||
end
|
end
|
||||||
|
|
@ -67,9 +79,11 @@ defmodule Pleroma.Object.Containment do
|
||||||
|
|
||||||
def contain_origin(id, %{"actor" => _actor} = params) do
|
def contain_origin(id, %{"actor" => _actor} = params) do
|
||||||
id_uri = URI.parse(id)
|
id_uri = URI.parse(id)
|
||||||
actor_uri = URI.parse(get_actor(params))
|
|
||||||
|
|
||||||
compare_uris(actor_uri, id_uri)
|
case get_actor(params) do
|
||||||
|
actor when is_binary(actor) -> compare_uris(URI.parse(actor), id_uri)
|
||||||
|
_ -> :error
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def contain_origin(id, %{"attributedTo" => actor} = params),
|
def contain_origin(id, %{"attributedTo" => actor} = params),
|
||||||
|
|
|
||||||
|
|
@ -309,7 +309,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
||||||
} = conn,
|
} = conn,
|
||||||
%{"nickname" => nickname} = params
|
%{"nickname" => nickname} = params
|
||||||
) do
|
) do
|
||||||
with {:recipient_exists, %User{} = recipient} <-
|
with false <- unsupported_http_message_signature?(conn),
|
||||||
|
{:recipient_exists, %User{} = recipient} <-
|
||||||
{:recipient_exists, User.get_cached_by_nickname(nickname)},
|
{:recipient_exists, User.get_cached_by_nickname(nickname)},
|
||||||
{:sender_exists, {:ok, %User{} = actor}} <-
|
{:sender_exists, {:ok, %User{} = actor}} <-
|
||||||
{:sender_exists, User.get_or_fetch_by_ap_id(params["actor"])},
|
{:sender_exists, User.get_or_fetch_by_ap_id(params["actor"])},
|
||||||
|
|
@ -320,6 +321,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
||||||
Federator.incoming_ap_doc(params)
|
Federator.incoming_ap_doc(params)
|
||||||
json(conn, "ok")
|
json(conn, "ok")
|
||||||
else
|
else
|
||||||
|
true ->
|
||||||
|
reject_unsupported_http_message_signature(conn)
|
||||||
|
|
||||||
{:recipient_exists, _} ->
|
{:recipient_exists, _} ->
|
||||||
conn
|
conn
|
||||||
|> put_status(:not_found)
|
|> put_status(:not_found)
|
||||||
|
|
@ -348,20 +352,28 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
||||||
end
|
end
|
||||||
|
|
||||||
def inbox(%{assigns: %{valid_signature: true, valid_host_header: true}} = conn, params) do
|
def inbox(%{assigns: %{valid_signature: true, valid_host_header: true}} = conn, params) do
|
||||||
Federator.incoming_ap_doc(params)
|
if unsupported_http_message_signature?(conn) do
|
||||||
json(conn, "ok")
|
reject_unsupported_http_message_signature(conn)
|
||||||
|
else
|
||||||
|
Federator.incoming_ap_doc(params)
|
||||||
|
json(conn, "ok")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def inbox(%{assigns: %{valid_signature: false}} = conn, params) do
|
def inbox(%{assigns: %{valid_signature: false}} = conn, params) do
|
||||||
Federator.incoming_failed_signature_ap_doc(%{
|
if unsupported_http_message_signature?(conn) do
|
||||||
method: conn.method,
|
reject_unsupported_http_message_signature(conn)
|
||||||
req_headers: conn.req_headers,
|
else
|
||||||
request_path: conn.request_path,
|
Federator.incoming_failed_signature_ap_doc(%{
|
||||||
params: params,
|
method: conn.method,
|
||||||
query_string: conn.query_string
|
req_headers: conn.req_headers,
|
||||||
})
|
request_path: conn.request_path,
|
||||||
|
params: params,
|
||||||
|
query_string: conn.query_string
|
||||||
|
})
|
||||||
|
|
||||||
json(conn, "ok")
|
json(conn, "ok")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# POST /relay/inbox -or- POST /internal/fetch/inbox
|
# POST /relay/inbox -or- POST /internal/fetch/inbox
|
||||||
|
|
@ -381,6 +393,27 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
||||||
|> json("error, missing HTTP Signature")
|
|> json("error, missing HTTP Signature")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp reject_unsupported_http_message_signature(conn) do
|
||||||
|
conn
|
||||||
|
|> put_status(:unauthorized)
|
||||||
|
|> json("error, unsupported HTTP Message Signature")
|
||||||
|
end
|
||||||
|
|
||||||
|
defp unsupported_http_message_signature?(conn) do
|
||||||
|
signature_headers = Plug.Conn.get_req_header(conn, "signature")
|
||||||
|
|
||||||
|
multiple_signature_headers?(signature_headers) or
|
||||||
|
(Plug.Conn.get_req_header(conn, "signature-input") != [] and
|
||||||
|
not cavage_signature?(signature_headers))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp multiple_signature_headers?([_, _ | _]), do: true
|
||||||
|
defp multiple_signature_headers?(_), do: false
|
||||||
|
|
||||||
|
defp cavage_signature?(signature_headers) do
|
||||||
|
Enum.any?(signature_headers, &Regex.match?(~r/(^|,)\s*keyId\s*=/, &1))
|
||||||
|
end
|
||||||
|
|
||||||
defp post_inbox_relayed_create(conn, params) do
|
defp post_inbox_relayed_create(conn, params) do
|
||||||
Logger.debug(
|
Logger.debug(
|
||||||
"Signature missing or not from author, relayed Create message, fetching object from source"
|
"Signature missing or not from author, relayed Create message, fetching object from source"
|
||||||
|
|
|
||||||
|
|
@ -118,6 +118,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
||||||
when objtype in ~w[Question Answer Audio Video Image Event Article Note Page] do
|
when objtype in ~w[Question Answer Audio Video Image Event Article Note Page] do
|
||||||
with {:ok, object_data} <-
|
with {:ok, object_data} <-
|
||||||
object
|
object
|
||||||
|
|> CommonFixes.maybe_set_attributed_to_from_activity(create_activity)
|
||||||
|> CommonFixes.maybe_add_language_from_activity(create_activity)
|
|> CommonFixes.maybe_add_language_from_activity(create_activity)
|
||||||
|> cast_and_apply_and_stringify_with_history(),
|
|> cast_and_apply_and_stringify_with_history(),
|
||||||
meta = Keyword.put(meta, :object_data, object_data),
|
meta = Keyword.put(meta, :object_data, object_data),
|
||||||
|
|
@ -172,6 +173,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
||||||
{_, {:ok, object_data, _}} <-
|
{_, {:ok, object_data, _}} <-
|
||||||
{:object_validation,
|
{:object_validation,
|
||||||
object
|
object
|
||||||
|
|> CommonFixes.maybe_set_attributed_to_from_activity(update_activity)
|
||||||
|> CommonFixes.maybe_add_language_from_activity(update_activity)
|
|> CommonFixes.maybe_add_language_from_activity(update_activity)
|
||||||
|> validate(meta)},
|
|> validate(meta)},
|
||||||
meta = Keyword.put(meta, :object_data, object_data),
|
meta = Keyword.put(meta, :object_data, object_data),
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,32 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp fix_url(%{"url" => url} = data) when is_bitstring(url), do: data
|
defp fix_url(%{"url" => url} = data) when is_bitstring(url), do: data
|
||||||
defp fix_url(%{"url" => url} = data) when is_map(url), do: Map.put(data, "url", url["href"])
|
|
||||||
|
defp fix_url(%{"url" => url} = data) when is_map(url) do
|
||||||
|
if is_binary(url["href"]) do
|
||||||
|
Map.put(data, "url", url["href"])
|
||||||
|
else
|
||||||
|
Map.delete(data, "url")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp fix_url(%{"url" => url} = data) when is_list(url) do
|
||||||
|
first_element = Enum.at(url, 0)
|
||||||
|
|
||||||
|
url_string =
|
||||||
|
cond do
|
||||||
|
is_bitstring(first_element) -> first_element
|
||||||
|
is_map(first_element) -> first_element["href"]
|
||||||
|
true -> nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if is_binary(url_string) do
|
||||||
|
Map.put(data, "url", url_string)
|
||||||
|
else
|
||||||
|
Map.delete(data, "url")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp fix_url(data), do: data
|
defp fix_url(data), do: data
|
||||||
|
|
||||||
defp fix_tag(%{"tag" => tag} = data) when is_list(tag) do
|
defp fix_tag(%{"tag" => tag} = data) when is_list(tag) do
|
||||||
|
|
@ -79,10 +104,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do
|
||||||
|
|
||||||
defp fix_replies(data), do: Map.delete(data, "replies")
|
defp fix_replies(data), do: Map.delete(data, "replies")
|
||||||
|
|
||||||
def fix_attachments(%{"attachment" => attachment} = data) when is_map(attachment),
|
def fix_attachments(data), do: Transmogrifier.fix_attachments(data)
|
||||||
do: Map.put(data, "attachment", [attachment])
|
|
||||||
|
|
||||||
def fix_attachments(data), do: data
|
|
||||||
|
|
||||||
defp remote_mention_resolver(
|
defp remote_mention_resolver(
|
||||||
%{"id" => ap_id, "tag" => tags},
|
%{"id" => ap_id, "tag" => tags},
|
||||||
|
|
|
||||||
|
|
@ -41,22 +41,34 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do
|
||||||
def fix_object_defaults(data) do
|
def fix_object_defaults(data) do
|
||||||
data = Maps.filter_empty_values(data)
|
data = Maps.filter_empty_values(data)
|
||||||
|
|
||||||
|
in_reply_to_id = in_reply_to_id(data["inReplyTo"])
|
||||||
|
|
||||||
context =
|
context =
|
||||||
Utils.maybe_create_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
|
with attributed_to when is_binary(attributed_to) <- data["attributedTo"],
|
||||||
|> Map.put("context", context)
|
%User{follower_address: follower_collection} <- User.get_cached_by_ap_id(attributed_to) do
|
||||||
|> cast_and_filter_recipients("to", follower_collection)
|
data
|
||||||
|> cast_and_filter_recipients("cc", follower_collection)
|
|> cast_and_filter_recipients("to", follower_collection)
|
||||||
|> cast_and_filter_recipients("bto", follower_collection)
|
|> cast_and_filter_recipients("cc", follower_collection)
|
||||||
|> cast_and_filter_recipients("bcc", follower_collection)
|
|> cast_and_filter_recipients("bto", follower_collection)
|
||||||
|> Transmogrifier.fix_implicit_addressing(follower_collection)
|
|> cast_and_filter_recipients("bcc", follower_collection)
|
||||||
|
|> Transmogrifier.fix_implicit_addressing(follower_collection)
|
||||||
|
else
|
||||||
|
_ -> data
|
||||||
|
end
|
||||||
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
|
def fix_activity_addressing(activity) do
|
||||||
%User{follower_address: follower_collection} = User.get_cached_by_ap_id(activity["actor"])
|
%User{follower_address: follower_collection} = User.get_cached_by_ap_id(activity["actor"])
|
||||||
|
|
||||||
|
|
@ -79,6 +91,17 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do
|
||||||
|> Map.put("attributedTo", actor)
|
|> Map.put("attributedTo", actor)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def maybe_set_attributed_to_from_activity(object, activity) when is_map(object) do
|
||||||
|
if is_nil(object["attributedTo"]) and is_nil(object["actor"]) do
|
||||||
|
case Containment.get_actor(activity) do
|
||||||
|
actor when is_binary(actor) -> Map.put(object, "attributedTo", actor)
|
||||||
|
_ -> object
|
||||||
|
end
|
||||||
|
else
|
||||||
|
object
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def fix_activity_context(data, %Object{data: %{"context" => object_context}}) do
|
def fix_activity_context(data, %Object{data: %{"context" => object_context}}) do
|
||||||
data
|
data
|
||||||
|> Map.put("context", object_context)
|
|> Map.put("context", object_context)
|
||||||
|
|
@ -96,23 +119,37 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do
|
||||||
Map.put(data, "to", to)
|
Map.put(data, "to", to)
|
||||||
end
|
end
|
||||||
|
|
||||||
def fix_quote_url(%{"quoteUrl" => _quote_url} = data), do: data
|
def fix_quote_url(%{"quoteUrl" => quote_url} = data) do
|
||||||
|
case normalize_object_id(quote_url) do
|
||||||
|
quote_url when is_binary(quote_url) -> Map.put(data, "quoteUrl", quote_url)
|
||||||
|
_ -> Map.delete(data, "quoteUrl")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Fedibird
|
# Fedibird
|
||||||
# https://github.com/fedibird/mastodon/commit/dbd7ae6cf58a92ec67c512296b4daaea0d01e6ac
|
# https://github.com/fedibird/mastodon/commit/dbd7ae6cf58a92ec67c512296b4daaea0d01e6ac
|
||||||
def fix_quote_url(%{"quoteUri" => quote_url} = data) do
|
def fix_quote_url(%{"quoteUri" => quote_url} = data) do
|
||||||
Map.put(data, "quoteUrl", quote_url)
|
case normalize_object_id(quote_url) do
|
||||||
|
quote_url when is_binary(quote_url) -> Map.put(data, "quoteUrl", quote_url)
|
||||||
|
_ -> data
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Old Fedibird (bug)
|
# Old Fedibird (bug)
|
||||||
# https://github.com/fedibird/mastodon/issues/9
|
# https://github.com/fedibird/mastodon/issues/9
|
||||||
def fix_quote_url(%{"quoteURL" => quote_url} = data) do
|
def fix_quote_url(%{"quoteURL" => quote_url} = data) do
|
||||||
Map.put(data, "quoteUrl", quote_url)
|
case normalize_object_id(quote_url) do
|
||||||
|
quote_url when is_binary(quote_url) -> Map.put(data, "quoteUrl", quote_url)
|
||||||
|
_ -> data
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Misskey fallback
|
# Misskey fallback
|
||||||
def fix_quote_url(%{"_misskey_quote" => quote_url} = data) do
|
def fix_quote_url(%{"_misskey_quote" => quote_url} = data) do
|
||||||
Map.put(data, "quoteUrl", quote_url)
|
case normalize_object_id(quote_url) do
|
||||||
|
quote_url when is_binary(quote_url) -> Map.put(data, "quoteUrl", quote_url)
|
||||||
|
_ -> data
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def fix_quote_url(%{"tag" => [_ | _] = tags} = data) do
|
def fix_quote_url(%{"tag" => [_ | _] = tags} = data) do
|
||||||
|
|
@ -128,6 +165,11 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do
|
||||||
|
|
||||||
def fix_quote_url(data), do: data
|
def fix_quote_url(data), do: data
|
||||||
|
|
||||||
|
defp normalize_object_id(object_id) when is_binary(object_id), do: object_id
|
||||||
|
defp normalize_object_id(%{"id" => object_id}) when is_binary(object_id), do: object_id
|
||||||
|
defp normalize_object_id([object_id | _]) when is_binary(object_id), do: object_id
|
||||||
|
defp normalize_object_id(_), do: nil
|
||||||
|
|
||||||
# On Mastodon, `"likes"` attribute includes an inlined `Collection` with `totalItems`,
|
# On Mastodon, `"likes"` attribute includes an inlined `Collection` with `totalItems`,
|
||||||
# not a list of users.
|
# not a list of users.
|
||||||
# https://github.com/mastodon/mastodon/pull/32007
|
# https://github.com/mastodon/mastodon/pull/32007
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.TagValidator do
|
||||||
require Pleroma.Constants
|
require Pleroma.Constants
|
||||||
|
|
||||||
@primary_key false
|
@primary_key false
|
||||||
|
@tag_types ~w[Mention Hashtag Emoji Link]
|
||||||
|
|
||||||
embedded_schema do
|
embedded_schema do
|
||||||
# Common
|
# Common
|
||||||
field(:type, :string)
|
field(:type, :string)
|
||||||
|
|
@ -49,7 +51,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.TagValidator do
|
||||||
|> validate_required([:type, :href])
|
|> validate_required([:type, :href])
|
||||||
end
|
end
|
||||||
|
|
||||||
def changeset(struct, %{"type" => "Hashtag", "name" => name} = data) do
|
def changeset(struct, %{"type" => "Hashtag", "name" => name} = data) when is_binary(name) do
|
||||||
name = String.downcase(name)
|
name = String.downcase(name)
|
||||||
data = Map.put(data, "name", name)
|
data = Map.put(data, "name", name)
|
||||||
|
|
||||||
|
|
@ -58,13 +60,42 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.TagValidator do
|
||||||
|> validate_required([:type, :name])
|
|> validate_required([:type, :name])
|
||||||
end
|
end
|
||||||
|
|
||||||
def changeset(struct, %{"type" => "Emoji"} = data) do
|
def changeset(struct, %{"type" => "Hashtag"} = data) do
|
||||||
data = Map.put(data, "name", String.trim(data["name"], ":"))
|
|
||||||
|
|
||||||
struct
|
struct
|
||||||
|> cast(data, [:type, :name, :updated, :id])
|
|> cast(data, [])
|
||||||
|> cast_embed(:icon, with: &icon_changeset/2)
|
|> Map.put(:action, :ignore)
|
||||||
|> validate_required([:type, :name, :icon])
|
end
|
||||||
|
|
||||||
|
def changeset(struct, %{"type" => "Emoji", "name" => name} = data) when is_binary(name) do
|
||||||
|
data =
|
||||||
|
data
|
||||||
|
|> Map.put("name", String.trim(data["name"], ":"))
|
||||||
|
|> normalize_emoji_icon()
|
||||||
|
|
||||||
|
case data["icon"] do
|
||||||
|
%{"url" => url} when is_binary(url) ->
|
||||||
|
if valid_http_url?(url) do
|
||||||
|
struct
|
||||||
|
|> cast(data, [:type, :name, :updated, :id])
|
||||||
|
|> cast_embed(:icon, with: &icon_changeset/2)
|
||||||
|
|> validate_required([:type, :name, :icon])
|
||||||
|
else
|
||||||
|
struct
|
||||||
|
|> cast(data, [])
|
||||||
|
|> Map.put(:action, :ignore)
|
||||||
|
end
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
struct
|
||||||
|
|> cast(data, [])
|
||||||
|
|> Map.put(:action, :ignore)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def changeset(struct, %{"type" => "Emoji"} = data) do
|
||||||
|
struct
|
||||||
|
|> cast(data, [])
|
||||||
|
|> Map.put(:action, :ignore)
|
||||||
end
|
end
|
||||||
|
|
||||||
def changeset(struct, %{"type" => "Link"} = data) do
|
def changeset(struct, %{"type" => "Link"} = data) do
|
||||||
|
|
@ -74,12 +105,80 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.TagValidator do
|
||||||
|> validate_required([:type, :href, :mediaType])
|
|> validate_required([:type, :href, :mediaType])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def changeset(struct, %{"type" => types} = data) when is_list(types) do
|
||||||
|
changeset(struct, infer_type(data))
|
||||||
|
end
|
||||||
|
|
||||||
def changeset(struct, %{"type" => _} = data) do
|
def changeset(struct, %{"type" => _} = data) do
|
||||||
struct
|
struct
|
||||||
|> cast(data, [])
|
|> cast(data, [])
|
||||||
|> Map.put(:action, :ignore)
|
|> Map.put(:action, :ignore)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def changeset(struct, data) when is_map(data) do
|
||||||
|
data = normalize(data)
|
||||||
|
|
||||||
|
if Map.has_key?(data, "type") do
|
||||||
|
changeset(struct, data)
|
||||||
|
else
|
||||||
|
struct
|
||||||
|
|> cast(data, [])
|
||||||
|
|> Map.put(:action, :ignore)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def normalize(data) when is_map(data), do: infer_type(data)
|
||||||
|
|
||||||
|
defp infer_type(%{"type" => types} = data) when is_list(types) do
|
||||||
|
type = Enum.find(types, &(&1 in @tag_types)) || Enum.find(types, &is_binary/1)
|
||||||
|
|
||||||
|
if type, do: Map.put(data, "type", type), else: Map.delete(data, "type")
|
||||||
|
end
|
||||||
|
|
||||||
|
defp infer_type(%{"type" => _} = data), do: data
|
||||||
|
defp infer_type(%{"name" => "#" <> _} = data), do: Map.put(data, "type", "Hashtag")
|
||||||
|
defp infer_type(%{"name" => "@" <> _} = data), do: Map.put(data, "type", "Mention")
|
||||||
|
|
||||||
|
defp infer_type(%{"mediaType" => _media_type, "href" => _href} = data),
|
||||||
|
do: Map.put(data, "type", "Link")
|
||||||
|
|
||||||
|
defp infer_type(data), do: data
|
||||||
|
|
||||||
|
defp normalize_emoji_icon(%{"icon" => icon} = data) when is_binary(icon) do
|
||||||
|
Map.put(data, "icon", %{"type" => "Image", "url" => icon})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp normalize_emoji_icon(%{"icon" => icon} = data) when is_map(icon) do
|
||||||
|
case extract_icon_url(icon) do
|
||||||
|
url when is_binary(url) -> Map.put(data, "icon", %{"type" => "Image", "url" => url})
|
||||||
|
_ -> Map.delete(data, "icon")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp normalize_emoji_icon(data), do: data
|
||||||
|
|
||||||
|
defp extract_icon_url(%{"url" => url}) when is_binary(url), do: url
|
||||||
|
|
||||||
|
defp extract_icon_url(%{"url" => %{"href" => href}}) when is_binary(href), do: href
|
||||||
|
|
||||||
|
defp extract_icon_url(%{"url" => [first | _]}) do
|
||||||
|
cond do
|
||||||
|
is_binary(first) -> first
|
||||||
|
is_map(first) -> first["href"]
|
||||||
|
true -> nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp extract_icon_url(%{"href" => href}) when is_binary(href), do: href
|
||||||
|
|
||||||
|
defp extract_icon_url(_), do: nil
|
||||||
|
|
||||||
|
defp valid_http_url?(url) when is_binary(url) do
|
||||||
|
match?({:ok, _}, ObjectValidators.Uri.cast(url))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp valid_http_url?(_), do: false
|
||||||
|
|
||||||
def icon_changeset(struct, data) do
|
def icon_changeset(struct, data) do
|
||||||
struct
|
struct
|
||||||
|> cast(data, [:type, :url])
|
|> cast(data, [:type, :url])
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||||
alias Pleroma.Web.ActivityPub.Builder
|
alias Pleroma.Web.ActivityPub.Builder
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
|
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator
|
||||||
alias Pleroma.Web.ActivityPub.Pipeline
|
alias Pleroma.Web.ActivityPub.Pipeline
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
alias Pleroma.Web.ActivityPub.Visibility
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
|
|
@ -28,6 +29,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||||
|
|
||||||
require Pleroma.Constants
|
require Pleroma.Constants
|
||||||
|
|
||||||
|
@supported_types Pleroma.Constants.activity_types() ++
|
||||||
|
Pleroma.Constants.object_types() ++ Pleroma.Constants.actor_types()
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Modifies an incoming AP object (mastodon format) to our internal format.
|
Modifies an incoming AP object (mastodon format) to our internal format.
|
||||||
"""
|
"""
|
||||||
|
|
@ -201,9 +205,29 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||||
def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options)
|
def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options)
|
||||||
when not is_nil(in_reply_to) do
|
when not is_nil(in_reply_to) do
|
||||||
in_reply_to_id = prepare_in_reply_to(in_reply_to)
|
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
|
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),
|
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
|
%Activity{} <- Activity.get_create_by_object_ap_id(replied_object.data["id"]) do
|
||||||
object
|
object
|
||||||
|
|
@ -249,9 +273,17 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||||
is_map(in_reply_to) && is_bitstring(in_reply_to["id"]) ->
|
is_map(in_reply_to) && is_bitstring(in_reply_to["id"]) ->
|
||||||
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)) ->
|
is_list(in_reply_to) && is_bitstring(Enum.at(in_reply_to, 0)) ->
|
||||||
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 ->
|
true ->
|
||||||
""
|
""
|
||||||
end
|
end
|
||||||
|
|
@ -265,59 +297,74 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||||
|> Map.drop(["conversation"])
|
|> Map.drop(["conversation"])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp valid_http_url?(href) when is_binary(href) do
|
||||||
|
match?({:ok, _}, ObjectValidators.Uri.cast(href))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp valid_http_url?(_), do: false
|
||||||
|
|
||||||
def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachment) do
|
def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachment) do
|
||||||
attachments =
|
attachments =
|
||||||
Enum.map(attachment, fn data ->
|
Enum.map(attachment, fn
|
||||||
url =
|
data when is_map(data) ->
|
||||||
cond do
|
url =
|
||||||
is_list(data["url"]) -> List.first(data["url"])
|
cond do
|
||||||
is_map(data["url"]) -> data["url"]
|
is_list(data["url"]) -> List.first(data["url"])
|
||||||
true -> nil
|
is_map(data["url"]) -> data["url"]
|
||||||
end
|
true -> nil
|
||||||
|
end
|
||||||
|
|
||||||
media_type =
|
url_data = if is_map(url), do: url, else: %{}
|
||||||
cond do
|
|
||||||
is_map(url) && url =~ Pleroma.Constants.mime_regex() ->
|
|
||||||
url["mediaType"]
|
|
||||||
|
|
||||||
is_bitstring(data["mediaType"]) && data["mediaType"] =~ Pleroma.Constants.mime_regex() ->
|
media_type =
|
||||||
data["mediaType"]
|
cond do
|
||||||
|
is_bitstring(url_data["mediaType"]) &&
|
||||||
|
url_data["mediaType"] =~ Pleroma.Constants.mime_regex() ->
|
||||||
|
url_data["mediaType"]
|
||||||
|
|
||||||
is_bitstring(data["mimeType"]) && data["mimeType"] =~ Pleroma.Constants.mime_regex() ->
|
is_bitstring(data["mediaType"]) &&
|
||||||
data["mimeType"]
|
data["mediaType"] =~ Pleroma.Constants.mime_regex() ->
|
||||||
|
data["mediaType"]
|
||||||
|
|
||||||
true ->
|
is_bitstring(data["mimeType"]) && data["mimeType"] =~ Pleroma.Constants.mime_regex() ->
|
||||||
nil
|
data["mimeType"]
|
||||||
end
|
|
||||||
|
|
||||||
href =
|
true ->
|
||||||
cond do
|
nil
|
||||||
is_map(url) && is_binary(url["href"]) -> url["href"]
|
end
|
||||||
is_binary(data["url"]) -> data["url"]
|
|
||||||
is_binary(data["href"]) -> data["href"]
|
href =
|
||||||
true -> nil
|
cond do
|
||||||
end
|
is_binary(url) -> url
|
||||||
|
is_binary(url_data["href"]) -> url_data["href"]
|
||||||
|
is_binary(data["url"]) -> data["url"]
|
||||||
|
is_binary(data["href"]) -> data["href"]
|
||||||
|
true -> nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if href && valid_http_url?(href) do
|
||||||
|
attachment_url =
|
||||||
|
%{
|
||||||
|
"href" => href,
|
||||||
|
"type" => Map.get(url_data, "type", "Link")
|
||||||
|
}
|
||||||
|
|> Maps.put_if_present("mediaType", media_type)
|
||||||
|
|> Maps.put_if_present("width", url_data["width"] || data["width"])
|
||||||
|
|> Maps.put_if_present("height", url_data["height"] || data["height"])
|
||||||
|
|
||||||
if href do
|
|
||||||
attachment_url =
|
|
||||||
%{
|
%{
|
||||||
"href" => href,
|
"url" => [attachment_url],
|
||||||
"type" => Map.get(url || %{}, "type", "Link")
|
"type" => data["type"] || "Document"
|
||||||
}
|
}
|
||||||
|> Maps.put_if_present("mediaType", media_type)
|
|> Maps.put_if_present("mediaType", media_type)
|
||||||
|> Maps.put_if_present("width", (url || %{})["width"] || data["width"])
|
|> Maps.put_if_present("name", data["name"])
|
||||||
|> Maps.put_if_present("height", (url || %{})["height"] || data["height"])
|
|> Maps.put_if_present("blurhash", data["blurhash"])
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
%{
|
_ ->
|
||||||
"url" => [attachment_url],
|
|
||||||
"type" => data["type"] || "Document"
|
|
||||||
}
|
|
||||||
|> Maps.put_if_present("mediaType", media_type)
|
|
||||||
|> Maps.put_if_present("name", data["name"])
|
|
||||||
|> Maps.put_if_present("blurhash", data["blurhash"])
|
|
||||||
else
|
|
||||||
nil
|
nil
|
||||||
end
|
|
||||||
end)
|
end)
|
||||||
|> Enum.filter(& &1)
|
|> Enum.filter(& &1)
|
||||||
|
|
||||||
|
|
@ -351,24 +398,58 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||||
|
|
||||||
def fix_url(object), do: object
|
def fix_url(object), do: object
|
||||||
|
|
||||||
|
defp emoji_icon_url(icon) when is_binary(icon), do: icon
|
||||||
|
|
||||||
|
defp emoji_icon_url(%{"url" => url}) when is_binary(url), do: url
|
||||||
|
|
||||||
|
defp emoji_icon_url(%{"url" => %{"href" => href}}) when is_binary(href), do: href
|
||||||
|
|
||||||
|
defp emoji_icon_url(%{"url" => [first | _]}) do
|
||||||
|
cond do
|
||||||
|
is_binary(first) -> first
|
||||||
|
is_map(first) -> first["href"] || first["url"] || first["id"]
|
||||||
|
true -> nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp emoji_icon_url(%{"href" => href}) when is_binary(href), do: href
|
||||||
|
|
||||||
|
defp emoji_icon_url(_), do: nil
|
||||||
|
|
||||||
|
defp valid_emoji_icon_url(icon) do
|
||||||
|
case emoji_icon_url(icon) do
|
||||||
|
url when is_binary(url) -> if valid_http_url?(url), do: url
|
||||||
|
_ -> nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def fix_emoji(%{"tag" => tags} = object) when is_list(tags) do
|
def fix_emoji(%{"tag" => tags} = object) when is_list(tags) do
|
||||||
emoji =
|
emoji =
|
||||||
tags
|
tags
|
||||||
|> Enum.filter(fn data -> is_map(data) and data["type"] == "Emoji" and data["icon"] end)
|
|> Enum.filter(fn data ->
|
||||||
|
is_map(data) and data["type"] == "Emoji" and is_binary(data["name"]) and data["icon"]
|
||||||
|
end)
|
||||||
|> Enum.reduce(%{}, fn data, mapping ->
|
|> Enum.reduce(%{}, fn data, mapping ->
|
||||||
name = String.trim(data["name"], ":")
|
name = String.trim(data["name"], ":")
|
||||||
|
|
||||||
Map.put(mapping, name, data["icon"]["url"])
|
case valid_emoji_icon_url(data["icon"]) do
|
||||||
|
url when is_binary(url) -> Map.put(mapping, name, url)
|
||||||
|
_ -> mapping
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
Map.put(object, "emoji", emoji)
|
Map.put(object, "emoji", emoji)
|
||||||
end
|
end
|
||||||
|
|
||||||
def fix_emoji(%{"tag" => %{"type" => "Emoji"} = tag} = object) do
|
def fix_emoji(%{"tag" => %{"type" => "Emoji", "name" => name} = tag} = object)
|
||||||
name = String.trim(tag["name"], ":")
|
when is_binary(name) do
|
||||||
emoji = %{name => tag["icon"]["url"]}
|
name = String.trim(name, ":")
|
||||||
|
|
||||||
Map.put(object, "emoji", emoji)
|
with url when is_binary(url) <- valid_emoji_icon_url(tag["icon"]) do
|
||||||
|
Map.put(object, "emoji", %{name => url})
|
||||||
|
else
|
||||||
|
_ -> object
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def fix_emoji(object), do: object
|
def fix_emoji(object), do: object
|
||||||
|
|
@ -376,7 +457,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||||
def fix_tag(%{"tag" => tag} = object) when is_list(tag) do
|
def fix_tag(%{"tag" => tag} = object) when is_list(tag) do
|
||||||
tags =
|
tags =
|
||||||
tag
|
tag
|
||||||
|> Enum.filter(fn data -> data["type"] == "Hashtag" and data["name"] end)
|
|> Enum.filter(fn data ->
|
||||||
|
is_map(data) and data["type"] == "Hashtag" and is_binary(data["name"])
|
||||||
|
end)
|
||||||
|> Enum.map(fn
|
|> Enum.map(fn
|
||||||
%{"name" => "#" <> hashtag} -> String.downcase(hashtag)
|
%{"name" => "#" <> hashtag} -> String.downcase(hashtag)
|
||||||
%{"name" => hashtag} -> String.downcase(hashtag)
|
%{"name" => hashtag} -> String.downcase(hashtag)
|
||||||
|
|
@ -439,9 +522,37 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||||
def handle_incoming(data, options \\ []) do
|
def handle_incoming(data, options \\ []) do
|
||||||
data
|
data
|
||||||
|> fix_recursive(&strip_internal_fields/1)
|
|> fix_recursive(&strip_internal_fields/1)
|
||||||
|
|> fix_recursive(&fix_type_as_string/1)
|
||||||
|> handle_incoming_normalized(options)
|
|> handle_incoming_normalized(options)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp fix_type_as_string(data) do
|
||||||
|
data
|
||||||
|
|> normalize_type()
|
||||||
|
|> normalize_tag_types()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp normalize_type(%{"type" => types} = data) when is_list(types) do
|
||||||
|
type = Enum.find(types, &(&1 in @supported_types)) || Enum.find(types, &is_binary/1)
|
||||||
|
|
||||||
|
if type, do: Map.put(data, "type", type), else: data
|
||||||
|
end
|
||||||
|
|
||||||
|
defp normalize_type(data), do: data
|
||||||
|
|
||||||
|
defp normalize_tag_types(%{"tag" => tags} = data) when is_list(tags) do
|
||||||
|
Map.put(data, "tag", Enum.map(tags, &normalize_tag_type/1))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp normalize_tag_types(%{"tag" => tag} = data) when is_map(tag) do
|
||||||
|
Map.put(data, "tag", normalize_tag_type(tag))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp normalize_tag_types(data), do: data
|
||||||
|
|
||||||
|
defp normalize_tag_type(tag) when is_map(tag), do: TagValidator.normalize(tag)
|
||||||
|
defp normalize_tag_type(tag), do: tag
|
||||||
|
|
||||||
# Flag objects are placed ahead of the ID check because Mastodon 2.8 and earlier send them
|
# Flag objects are placed ahead of the ID check because Mastodon 2.8 and earlier send them
|
||||||
# with nil ID.
|
# with nil ID.
|
||||||
defp handle_incoming_normalized(
|
defp handle_incoming_normalized(
|
||||||
|
|
|
||||||
32
test/fixtures/fedidev.fun/attachment_embedded_note.json
vendored
Normal file
32
test/fixtures/fedidev.fun/attachment_embedded_note.json
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
{
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams"
|
||||||
|
],
|
||||||
|
"type": "Create",
|
||||||
|
"id": "https://fedidev.fun/activity/attachment-embedded-note",
|
||||||
|
"actor": "https://fedidev.fun/users/alice",
|
||||||
|
"to": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
],
|
||||||
|
"cc": [],
|
||||||
|
"published": "2025-11-21T14:43:01Z",
|
||||||
|
"object": {
|
||||||
|
"type": "Note",
|
||||||
|
"id": "https://fedidev.fun/objects/note-attachment-embedded-note",
|
||||||
|
"attributedTo": "https://fedidev.fun/users/alice",
|
||||||
|
"to": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
],
|
||||||
|
"cc": [],
|
||||||
|
"published": "2025-11-21T14:43:01Z",
|
||||||
|
"content": "Note with embedded Note attachment",
|
||||||
|
"attachment": {
|
||||||
|
"type": "Note",
|
||||||
|
"attributedTo": "https://fedidev.fun/users/alice",
|
||||||
|
"name": "attached note",
|
||||||
|
"content": "This is just a note",
|
||||||
|
"published": "2024-03-06T07:23:56Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
31
test/fixtures/fedidev.fun/attachment_payto_link.json
vendored
Normal file
31
test/fixtures/fedidev.fun/attachment_payto_link.json
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
{
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams"
|
||||||
|
],
|
||||||
|
"type": "Create",
|
||||||
|
"id": "https://fedidev.fun/activity/attachment-payto-link",
|
||||||
|
"actor": "https://fedidev.fun/users/alice",
|
||||||
|
"to": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
],
|
||||||
|
"cc": [],
|
||||||
|
"published": "2025-11-21T14:43:00Z",
|
||||||
|
"object": {
|
||||||
|
"type": "Note",
|
||||||
|
"id": "https://fedidev.fun/objects/note-attachment-payto-link",
|
||||||
|
"attributedTo": "https://fedidev.fun/users/alice",
|
||||||
|
"to": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
],
|
||||||
|
"cc": [],
|
||||||
|
"published": "2025-11-21T14:43:00Z",
|
||||||
|
"content": "Payment Link",
|
||||||
|
"attachment": {
|
||||||
|
"type": "Link",
|
||||||
|
"name": "Donate",
|
||||||
|
"href": "payto://iban/DE75512108001245126199",
|
||||||
|
"rel": "payment"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
40
test/fixtures/fedidev.fun/attachment_url_list.json
vendored
Normal file
40
test/fixtures/fedidev.fun/attachment_url_list.json
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
{
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams"
|
||||||
|
],
|
||||||
|
"type": "Create",
|
||||||
|
"id": "https://fedidev.fun/activity/attachment-url-list",
|
||||||
|
"actor": "https://fedidev.fun/users/alice",
|
||||||
|
"to": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
],
|
||||||
|
"cc": [],
|
||||||
|
"published": "2025-11-21T14:43:02Z",
|
||||||
|
"object": {
|
||||||
|
"type": "Note",
|
||||||
|
"id": "https://fedidev.fun/objects/note-attachment-url-list",
|
||||||
|
"attributedTo": "https://fedidev.fun/users/alice",
|
||||||
|
"to": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
],
|
||||||
|
"cc": [],
|
||||||
|
"published": "2025-11-21T14:43:02Z",
|
||||||
|
"content": "url is array of two Link objects",
|
||||||
|
"attachment": {
|
||||||
|
"type": "Image",
|
||||||
|
"url": [
|
||||||
|
{
|
||||||
|
"type": "Link",
|
||||||
|
"href": "https://fedidev.fun/images/007.png",
|
||||||
|
"mediaType": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Link",
|
||||||
|
"href": "https://fedidev.fun/images/008.jpg",
|
||||||
|
"mediaType": "image/jpeg"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
32
test/fixtures/fedidev.fun/emoji_icon_string.json
vendored
Normal file
32
test/fixtures/fedidev.fun/emoji_icon_string.json
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
{
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams"
|
||||||
|
],
|
||||||
|
"type": "Create",
|
||||||
|
"id": "https://fedidev.fun/activity/emoji-icon-string",
|
||||||
|
"actor": "https://fedidev.fun/users/alice",
|
||||||
|
"to": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
],
|
||||||
|
"cc": [],
|
||||||
|
"published": "2025-11-21T14:40:00Z",
|
||||||
|
"object": {
|
||||||
|
"type": "Note",
|
||||||
|
"id": "https://fedidev.fun/objects/note-emoji-icon-string",
|
||||||
|
"attributedTo": "https://fedidev.fun/users/alice",
|
||||||
|
"to": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
],
|
||||||
|
"cc": [],
|
||||||
|
"published": "2025-11-21T14:40:00Z",
|
||||||
|
"content": "emoji icon as string :cow:",
|
||||||
|
"tag": [
|
||||||
|
{
|
||||||
|
"type": "Emoji",
|
||||||
|
"name": ":cow:",
|
||||||
|
"icon": "https://fedidev.fun/static/cow.png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
34
test/fixtures/fedidev.fun/emoji_icon_url_missing_type.json
vendored
Normal file
34
test/fixtures/fedidev.fun/emoji_icon_url_missing_type.json
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
{
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams"
|
||||||
|
],
|
||||||
|
"type": "Create",
|
||||||
|
"id": "https://fedidev.fun/activity/emoji-icon-url-missing-type",
|
||||||
|
"actor": "https://fedidev.fun/users/alice",
|
||||||
|
"to": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
],
|
||||||
|
"cc": [],
|
||||||
|
"published": "2025-11-21T14:40:01Z",
|
||||||
|
"object": {
|
||||||
|
"type": "Note",
|
||||||
|
"id": "https://fedidev.fun/objects/note-emoji-icon-url-missing-type",
|
||||||
|
"attributedTo": "https://fedidev.fun/users/alice",
|
||||||
|
"to": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
],
|
||||||
|
"cc": [],
|
||||||
|
"published": "2025-11-21T14:40:01Z",
|
||||||
|
"content": "emoji icon url missing type :cow2:",
|
||||||
|
"tag": [
|
||||||
|
{
|
||||||
|
"type": "Emoji",
|
||||||
|
"name": ":cow2:",
|
||||||
|
"icon": {
|
||||||
|
"url": "https://fedidev.fun/static/cow.png"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
31
test/fixtures/fedidev.fun/emoji_missing_icon.json
vendored
Normal file
31
test/fixtures/fedidev.fun/emoji_missing_icon.json
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
{
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams"
|
||||||
|
],
|
||||||
|
"type": "Create",
|
||||||
|
"id": "https://fedidev.fun/activity/emoji-missing-icon",
|
||||||
|
"actor": "https://fedidev.fun/users/alice",
|
||||||
|
"to": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
],
|
||||||
|
"cc": [],
|
||||||
|
"published": "2025-11-21T14:40:02Z",
|
||||||
|
"object": {
|
||||||
|
"type": "Note",
|
||||||
|
"id": "https://fedidev.fun/objects/note-emoji-missing-icon",
|
||||||
|
"attributedTo": "https://fedidev.fun/users/alice",
|
||||||
|
"to": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
],
|
||||||
|
"cc": [],
|
||||||
|
"published": "2025-11-21T14:40:02Z",
|
||||||
|
"content": "emoji missing icon :cow3:",
|
||||||
|
"tag": [
|
||||||
|
{
|
||||||
|
"type": "Emoji",
|
||||||
|
"name": ":cow3:"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
25
test/fixtures/fedidev.fun/missing_attributed_to.json
vendored
Normal file
25
test/fixtures/fedidev.fun/missing_attributed_to.json
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams"
|
||||||
|
],
|
||||||
|
"type": "Create",
|
||||||
|
"id": "https://fedidev.fun/activity/missing-attributedto",
|
||||||
|
"actor": "https://fedidev.fun/users/alice",
|
||||||
|
"to": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
],
|
||||||
|
"cc": [],
|
||||||
|
"published": "2025-11-21T14:42:00Z",
|
||||||
|
"object": {
|
||||||
|
"type": "Note",
|
||||||
|
"id": "https://fedidev.fun/objects/note-missing-attributedto",
|
||||||
|
"attributedTo": null,
|
||||||
|
"to": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
],
|
||||||
|
"cc": [],
|
||||||
|
"published": "2025-11-21T14:42:00Z",
|
||||||
|
"content": "object without attributedTo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
28
test/fixtures/fedidev.fun/misskey_quote_list.json
vendored
Normal file
28
test/fixtures/fedidev.fun/misskey_quote_list.json
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams"
|
||||||
|
],
|
||||||
|
"type": "Create",
|
||||||
|
"id": "https://fedidev.fun/activity/misskey-quote-list",
|
||||||
|
"actor": "https://fedidev.fun/users/alice",
|
||||||
|
"to": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
],
|
||||||
|
"cc": [],
|
||||||
|
"published": "2025-11-21T14:43:53Z",
|
||||||
|
"object": {
|
||||||
|
"type": "Note",
|
||||||
|
"id": "https://fedidev.fun/objects/note-misskey-quote-list",
|
||||||
|
"attributedTo": "https://fedidev.fun/users/alice",
|
||||||
|
"to": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
],
|
||||||
|
"cc": [],
|
||||||
|
"published": "2025-11-21T14:43:53Z",
|
||||||
|
"content": "_misskey_quote attribute as list",
|
||||||
|
"_misskey_quote": [
|
||||||
|
"https://fedidev.fun/assets/note1.jsonap",
|
||||||
|
"https://fedidev.fun/assets/note2.jsonap"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
28
test/fixtures/fedidev.fun/object_type_list.json
vendored
Normal file
28
test/fixtures/fedidev.fun/object_type_list.json
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams"
|
||||||
|
],
|
||||||
|
"type": "Create",
|
||||||
|
"id": "https://fedidev.fun/activity/object-type-list",
|
||||||
|
"actor": "https://fedidev.fun/users/alice",
|
||||||
|
"to": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
],
|
||||||
|
"cc": [],
|
||||||
|
"published": "2025-11-21T14:43:32Z",
|
||||||
|
"object": {
|
||||||
|
"type": [
|
||||||
|
"Note"
|
||||||
|
],
|
||||||
|
"id": "https://fedidev.fun/objects/note-object-type-list",
|
||||||
|
"attributedTo": "https://fedidev.fun/users/alice",
|
||||||
|
"to": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
],
|
||||||
|
"cc": [],
|
||||||
|
"published": "2025-11-21T14:43:32Z",
|
||||||
|
"name": "name",
|
||||||
|
"summary": "summary",
|
||||||
|
"content": "content"
|
||||||
|
}
|
||||||
|
}
|
||||||
28
test/fixtures/fedidev.fun/quote_uri_list.json
vendored
Normal file
28
test/fixtures/fedidev.fun/quote_uri_list.json
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams"
|
||||||
|
],
|
||||||
|
"type": "Create",
|
||||||
|
"id": "https://fedidev.fun/activity/quote-uri-list",
|
||||||
|
"actor": "https://fedidev.fun/users/alice",
|
||||||
|
"to": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
],
|
||||||
|
"cc": [],
|
||||||
|
"published": "2025-11-21T14:43:52Z",
|
||||||
|
"object": {
|
||||||
|
"type": "Note",
|
||||||
|
"id": "https://fedidev.fun/objects/note-quote-uri-list",
|
||||||
|
"attributedTo": "https://fedidev.fun/users/alice",
|
||||||
|
"to": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
],
|
||||||
|
"cc": [],
|
||||||
|
"published": "2025-11-21T14:43:52Z",
|
||||||
|
"content": "quoteUri attribute as list",
|
||||||
|
"quoteUri": [
|
||||||
|
"https://fedidev.fun/assets/note1.jsonap",
|
||||||
|
"https://fedidev.fun/assets/note2.jsonap"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
28
test/fixtures/fedidev.fun/quote_url_list.json
vendored
Normal file
28
test/fixtures/fedidev.fun/quote_url_list.json
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams"
|
||||||
|
],
|
||||||
|
"type": "Create",
|
||||||
|
"id": "https://fedidev.fun/activity/quote-url-list",
|
||||||
|
"actor": "https://fedidev.fun/users/alice",
|
||||||
|
"to": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
],
|
||||||
|
"cc": [],
|
||||||
|
"published": "2025-11-21T14:43:52Z",
|
||||||
|
"object": {
|
||||||
|
"type": "Note",
|
||||||
|
"id": "https://fedidev.fun/objects/note-quote-url-list",
|
||||||
|
"attributedTo": "https://fedidev.fun/users/alice",
|
||||||
|
"to": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
],
|
||||||
|
"cc": [],
|
||||||
|
"published": "2025-11-21T14:43:52Z",
|
||||||
|
"content": "quoteUrl attribute as list",
|
||||||
|
"quoteUrl": [
|
||||||
|
"https://fedidev.fun/assets/note1.jsonap",
|
||||||
|
"https://fedidev.fun/assets/note2.jsonap"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
30
test/fixtures/fedidev.fun/tag_hashtag_missing_type.json
vendored
Normal file
30
test/fixtures/fedidev.fun/tag_hashtag_missing_type.json
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams"
|
||||||
|
],
|
||||||
|
"type": "Create",
|
||||||
|
"id": "https://fedidev.fun/activity/tag-hashtag-missing-type",
|
||||||
|
"actor": "https://fedidev.fun/users/alice",
|
||||||
|
"to": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
],
|
||||||
|
"cc": [],
|
||||||
|
"published": "2025-11-21T14:41:00Z",
|
||||||
|
"object": {
|
||||||
|
"type": "Note",
|
||||||
|
"id": "https://fedidev.fun/objects/note-tag-hashtag-missing-type",
|
||||||
|
"attributedTo": "https://fedidev.fun/users/alice",
|
||||||
|
"to": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
],
|
||||||
|
"cc": [],
|
||||||
|
"published": "2025-11-21T14:41:00Z",
|
||||||
|
"content": "hashtag without type",
|
||||||
|
"tag": [
|
||||||
|
{
|
||||||
|
"name": "#Test"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
31
test/fixtures/fedidev.fun/tag_mention_missing_type.json
vendored
Normal file
31
test/fixtures/fedidev.fun/tag_mention_missing_type.json
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
{
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams"
|
||||||
|
],
|
||||||
|
"type": "Create",
|
||||||
|
"id": "https://fedidev.fun/activity/tag-mention-missing-type",
|
||||||
|
"actor": "https://fedidev.fun/users/alice",
|
||||||
|
"to": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
],
|
||||||
|
"cc": [],
|
||||||
|
"published": "2025-11-21T14:41:01Z",
|
||||||
|
"object": {
|
||||||
|
"type": "Note",
|
||||||
|
"id": "https://fedidev.fun/objects/note-tag-mention-missing-type",
|
||||||
|
"attributedTo": "https://fedidev.fun/users/alice",
|
||||||
|
"to": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
],
|
||||||
|
"cc": [],
|
||||||
|
"published": "2025-11-21T14:41:01Z",
|
||||||
|
"content": "mention without type",
|
||||||
|
"tag": [
|
||||||
|
{
|
||||||
|
"name": "@bob",
|
||||||
|
"href": "https://remote.example/users/bob"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
27
test/fixtures/fedidev.fun/url_list.json
vendored
Normal file
27
test/fixtures/fedidev.fun/url_list.json
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams"
|
||||||
|
],
|
||||||
|
"type": "Create",
|
||||||
|
"id": "https://fedidev.fun/activity/url-list",
|
||||||
|
"actor": "https://fedidev.fun/users/alice",
|
||||||
|
"to": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
],
|
||||||
|
"cc": [],
|
||||||
|
"published": "2025-11-21T14:39:06Z",
|
||||||
|
"object": {
|
||||||
|
"type": "Note",
|
||||||
|
"id": "https://fedidev.fun/objects/note-url-list",
|
||||||
|
"attributedTo": "https://fedidev.fun/users/alice",
|
||||||
|
"to": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
],
|
||||||
|
"cc": [],
|
||||||
|
"published": "2025-11-21T14:39:06Z",
|
||||||
|
"content": "text",
|
||||||
|
"url": [
|
||||||
|
"https://fedidev.fun/objects/123"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -27,6 +27,11 @@ defmodule Pleroma.Object.ContainmentTest do
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "rejects an empty actor list without crashing" do
|
||||||
|
assert :error ==
|
||||||
|
Containment.contain_origin("https://example.com/objects/1", %{"actor" => []})
|
||||||
|
end
|
||||||
|
|
||||||
test "contain_origin_from_id() catches obvious spoofing attempts" do
|
test "contain_origin_from_id() catches obvious spoofing attempts" do
|
||||||
data = %{
|
data = %{
|
||||||
"id" => "http://example.com/~alyssa/activities/1234.json"
|
"id" => "http://example.com/~alyssa/activities/1234.json"
|
||||||
|
|
|
||||||
|
|
@ -729,6 +729,22 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
assert Activity.get_by_ap_id(data["id"])
|
assert Activity.get_by_ap_id(data["id"])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "rejects duplicate Signature headers before processing signed inbox requests", %{
|
||||||
|
conn: conn
|
||||||
|
} do
|
||||||
|
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign_valid_signature_for_actor(data["actor"])
|
||||||
|
|> put_req_header("content-type", "application/activity+json")
|
||||||
|
|> Map.update!(:req_headers, &[{"signature", "sig1=:fake-signature:"} | &1])
|
||||||
|
|> post("/inbox", data)
|
||||||
|
|
||||||
|
assert "error, unsupported HTTP Message Signature" == json_response(conn, 401)
|
||||||
|
assert [] == all_enqueued(worker: ReceiverWorker)
|
||||||
|
end
|
||||||
|
|
||||||
test "it inserts an incoming activity into the database" <>
|
test "it inserts an incoming activity into the database" <>
|
||||||
"even if we can't fetch the user but have it in our db",
|
"even if we can't fetch the user but have it in our db",
|
||||||
%{conn: conn} do
|
%{conn: conn} do
|
||||||
|
|
@ -757,6 +773,120 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
assert Activity.get_by_ap_id(data["id"])
|
assert Activity.get_by_ap_id(data["id"])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "rejects unsupported HTTP Message Signatures so clients can fall back", %{conn: conn} do
|
||||||
|
data = %{
|
||||||
|
"type" => "Create",
|
||||||
|
"actor" => "https://activitypubbot.example/user/ok",
|
||||||
|
"id" => "https://activitypubbot.example/activities/rfc9421-create",
|
||||||
|
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
|
"cc" => [],
|
||||||
|
"object" => %{
|
||||||
|
"type" => "Note",
|
||||||
|
"id" => "https://activitypubbot.example/objects/rfc9421-note",
|
||||||
|
"actor" => "https://activitypubbot.example/user/ok",
|
||||||
|
"attributedTo" => "https://activitypubbot.example/user/ok",
|
||||||
|
"content" => "hello from activitypub-bot",
|
||||||
|
"published" => "2026-05-29T13:33:31Z",
|
||||||
|
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
|
"cc" => []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Mox.expect(Pleroma.StubbedHTTPSignaturesMock, :validate_conn, fn _conn -> false end)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/activity+json")
|
||||||
|
|> put_req_header("date", "Fri, 29 May 2026 13:33:31 GMT")
|
||||||
|
|> put_req_header("content-digest", "sha-256=:fake-digest:")
|
||||||
|
|> put_req_header(
|
||||||
|
"signature-input",
|
||||||
|
"sig1=(\"@method\" \"@target-uri\" \"date\" \"content-type\" \"content-digest\");keyid=\"https://activitypubbot.example/user/ok/publickey\";alg=\"rsa-v1_5-sha256\";created=1780061611"
|
||||||
|
)
|
||||||
|
|> put_req_header("signature", "sig1=:fake-signature:")
|
||||||
|
|> post("/inbox", data)
|
||||||
|
|
||||||
|
assert "error, unsupported HTTP Message Signature" == json_response(conn, 401)
|
||||||
|
assert [] == all_enqueued(worker: SignatureRetryWorker)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "rejects duplicate Signature headers so clients can fall back", %{conn: conn} do
|
||||||
|
data = %{
|
||||||
|
"type" => "Create",
|
||||||
|
"actor" => "https://activitypubbot.example/user/ok",
|
||||||
|
"id" => "https://activitypubbot.example/activities/duplicate-signature-create",
|
||||||
|
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
|
"cc" => [],
|
||||||
|
"object" => %{
|
||||||
|
"type" => "Note",
|
||||||
|
"id" => "https://activitypubbot.example/objects/duplicate-signature-note",
|
||||||
|
"actor" => "https://activitypubbot.example/user/ok",
|
||||||
|
"attributedTo" => "https://activitypubbot.example/user/ok",
|
||||||
|
"content" => "hello from activitypub-bot",
|
||||||
|
"published" => "2026-05-29T13:33:31Z",
|
||||||
|
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
|
"cc" => []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
legacy_signature =
|
||||||
|
"keyId=\"https://activitypubbot.example/user/ok#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date digest content-type\",signature=\"fake-signature\""
|
||||||
|
|
||||||
|
Mox.expect(Pleroma.StubbedHTTPSignaturesMock, :validate_conn, fn _conn -> false end)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/activity+json")
|
||||||
|
|> put_req_header("date", "Fri, 29 May 2026 13:33:31 GMT")
|
||||||
|
|> put_req_header("digest", "SHA-256=fake-digest")
|
||||||
|
|> put_req_header("signature", "sig1=:fake-signature:")
|
||||||
|
|> Map.update!(:req_headers, &[{"signature", legacy_signature} | &1])
|
||||||
|
|> post("/inbox", data)
|
||||||
|
|
||||||
|
assert "error, unsupported HTTP Message Signature" == json_response(conn, 401)
|
||||||
|
assert [] == all_enqueued(worker: SignatureRetryWorker)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "keeps failed-signature retry for legacy signatures with Signature-Input", %{conn: conn} do
|
||||||
|
data = %{
|
||||||
|
"type" => "Create",
|
||||||
|
"actor" => "https://activitypubbot.example/user/ok",
|
||||||
|
"id" => "https://activitypubbot.example/activities/legacy-create",
|
||||||
|
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
|
"cc" => [],
|
||||||
|
"object" => %{
|
||||||
|
"type" => "Note",
|
||||||
|
"id" => "https://activitypubbot.example/objects/legacy-note",
|
||||||
|
"actor" => "https://activitypubbot.example/user/ok",
|
||||||
|
"attributedTo" => "https://activitypubbot.example/user/ok",
|
||||||
|
"content" => "hello from activitypub-bot",
|
||||||
|
"published" => "2026-05-29T13:33:31Z",
|
||||||
|
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
|
"cc" => []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Mox.expect(Pleroma.StubbedHTTPSignaturesMock, :validate_conn, fn _conn -> false end)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/activity+json")
|
||||||
|
|> put_req_header("date", "Fri, 29 May 2026 13:33:31 GMT")
|
||||||
|
|> put_req_header("digest", "SHA-256=fake-digest")
|
||||||
|
|> put_req_header(
|
||||||
|
"signature-input",
|
||||||
|
"sig1=(\"@method\" \"@target-uri\" \"date\" \"content-type\" \"digest\");keyid=\"https://activitypubbot.example/user/ok/publickey\";alg=\"rsa-v1_5-sha256\";created=1780061611"
|
||||||
|
)
|
||||||
|
|> put_req_header(
|
||||||
|
"signature",
|
||||||
|
"keyId=\"https://activitypubbot.example/user/ok#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date digest content-type\",signature=\"fake-signature\""
|
||||||
|
)
|
||||||
|
|> post("/inbox", data)
|
||||||
|
|
||||||
|
assert "ok" == json_response(conn, 200)
|
||||||
|
assert [_] = all_enqueued(worker: SignatureRetryWorker)
|
||||||
|
end
|
||||||
|
|
||||||
test "does not create a forged post after failed signature retry", %{conn: conn} do
|
test "does not create a forged post after failed signature retry", %{conn: conn} do
|
||||||
alice = insert(:user, local: false, ap_id: "https://one.com/users/alice")
|
alice = insert(:user, local: false, ap_id: "https://one.com/users/alice")
|
||||||
bob = insert(:user, local: false, ap_id: "https://two.com/users/bob")
|
bob = insert(:user, local: false, ap_id: "https://two.com/users/bob")
|
||||||
|
|
@ -1320,6 +1450,20 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
assert Activity.get_by_ap_id(data["id"])
|
assert Activity.get_by_ap_id(data["id"])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "rejects duplicate Signature headers", %{conn: conn, data: data} do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign_valid_signature_for_actor(data["actor"])
|
||||||
|
|> put_req_header("content-type", "application/activity+json")
|
||||||
|
|> Map.update!(:req_headers, &[{"signature", "sig1=:fake-signature:"} | &1])
|
||||||
|
|> post("/users/#{user.nickname}/inbox", data)
|
||||||
|
|
||||||
|
assert "error, unsupported HTTP Message Signature" == json_response(conn, 401)
|
||||||
|
assert [] == all_enqueued(worker: ReceiverWorker)
|
||||||
|
end
|
||||||
|
|
||||||
test "it accepts messages with to as string instead of array", %{conn: conn, data: data} do
|
test "it accepts messages with to as string instead of array", %{conn: conn, data: data} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
# 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.FedidevFunAttachmentsTest 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_data!(data) do
|
||||||
|
ensure_remote_actor!(data["actor"])
|
||||||
|
|
||||||
|
assert {:ok, activity} = Transmogrifier.handle_incoming(data)
|
||||||
|
object = Object.normalize(activity.data["object"], fetch: false)
|
||||||
|
|
||||||
|
{activity, object}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp ingest_fixture!(rel_path) do
|
||||||
|
rel_path
|
||||||
|
|> File.read!()
|
||||||
|
|> Jason.decode!()
|
||||||
|
|> ingest_data!()
|
||||||
|
end
|
||||||
|
|
||||||
|
test "drops non-http(s) attachment href" do
|
||||||
|
{_activity, object} = ingest_fixture!("test/fixtures/fedidev.fun/attachment_payto_link.json")
|
||||||
|
assert object.data["attachment"] == []
|
||||||
|
end
|
||||||
|
|
||||||
|
test "drops embedded non-attachment objects" do
|
||||||
|
{_activity, object} =
|
||||||
|
ingest_fixture!("test/fixtures/fedidev.fun/attachment_embedded_note.json")
|
||||||
|
|
||||||
|
assert object.data["attachment"] == []
|
||||||
|
end
|
||||||
|
|
||||||
|
test "accepts attachment url as list and keeps the first link" do
|
||||||
|
{_activity, object} = ingest_fixture!("test/fixtures/fedidev.fun/attachment_url_list.json")
|
||||||
|
|
||||||
|
[attachment] = object.data["attachment"]
|
||||||
|
[%{"href" => href} | _] = attachment["url"]
|
||||||
|
|
||||||
|
assert href == "https://fedidev.fun/images/007.png"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "drops malformed attachment list entries" do
|
||||||
|
data = File.read!("test/fixtures/fedidev.fun/attachment_url_list.json") |> Jason.decode!()
|
||||||
|
data = update_in(data, ["object", "attachment"], &["malformed" | List.wrap(&1)])
|
||||||
|
|
||||||
|
{_activity, object} = ingest_data!(data)
|
||||||
|
|
||||||
|
assert length(object.data["attachment"]) == 1
|
||||||
|
end
|
||||||
|
|
||||||
|
test "accepts a URI string in an attachment url list" do
|
||||||
|
data = File.read!("test/fixtures/fedidev.fun/attachment_url_list.json") |> Jason.decode!()
|
||||||
|
|
||||||
|
data =
|
||||||
|
put_in(data, ["object", "attachment", "url"], [
|
||||||
|
"https://fedidev.fun/images/007.png"
|
||||||
|
])
|
||||||
|
|
||||||
|
{_activity, object} = ingest_data!(data)
|
||||||
|
|
||||||
|
assert [%{"url" => [%{"href" => "https://fedidev.fun/images/007.png"}]}] =
|
||||||
|
object.data["attachment"]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
# 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.FedidevFunEmojiTest 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_data!(data) do
|
||||||
|
ensure_remote_actor!(data["actor"])
|
||||||
|
|
||||||
|
assert {:ok, activity} = Transmogrifier.handle_incoming(data)
|
||||||
|
object = Object.normalize(activity.data["object"], fetch: false)
|
||||||
|
|
||||||
|
{activity, object}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp ingest_fixture!(rel_path) do
|
||||||
|
rel_path
|
||||||
|
|> File.read!()
|
||||||
|
|> Jason.decode!()
|
||||||
|
|> ingest_data!()
|
||||||
|
end
|
||||||
|
|
||||||
|
test "ingests Emoji tag with string icon" do
|
||||||
|
{_activity, object} = ingest_fixture!("test/fixtures/fedidev.fun/emoji_icon_string.json")
|
||||||
|
assert object.data["emoji"]["cow"] == "https://fedidev.fun/static/cow.png"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "ingests Emoji tag with icon missing type" do
|
||||||
|
{_activity, object} =
|
||||||
|
ingest_fixture!("test/fixtures/fedidev.fun/emoji_icon_url_missing_type.json")
|
||||||
|
|
||||||
|
assert object.data["emoji"]["cow2"] == "https://fedidev.fun/static/cow.png"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "ignores Emoji tag missing icon" do
|
||||||
|
{_activity, object} = ingest_fixture!("test/fixtures/fedidev.fun/emoji_missing_icon.json")
|
||||||
|
assert object.data["emoji"] == %{}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "ignores Emoji tag missing a string name" do
|
||||||
|
data = File.read!("test/fixtures/fedidev.fun/emoji_icon_string.json") |> Jason.decode!()
|
||||||
|
data = put_in(data, ["object", "tag", Access.at(0), "name"], nil)
|
||||||
|
|
||||||
|
{_activity, object} = ingest_data!(data)
|
||||||
|
|
||||||
|
assert object.data["emoji"] == %{}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "ignores Emoji tag with a non-http icon" do
|
||||||
|
data = File.read!("test/fixtures/fedidev.fun/emoji_icon_string.json") |> Jason.decode!()
|
||||||
|
data = put_in(data, ["object", "tag", Access.at(0), "icon"], "file:///tmp/cow.png")
|
||||||
|
|
||||||
|
{_activity, object} = ingest_data!(data)
|
||||||
|
|
||||||
|
assert object.data["emoji"] == %{}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "normalizes a list-typed Emoji tag before building the emoji map" do
|
||||||
|
data = File.read!("test/fixtures/fedidev.fun/emoji_icon_string.json") |> Jason.decode!()
|
||||||
|
|
||||||
|
data =
|
||||||
|
put_in(data, ["object", "tag", Access.at(0), "type"], [
|
||||||
|
"https://example.com/ns#CustomTag",
|
||||||
|
"Emoji"
|
||||||
|
])
|
||||||
|
|
||||||
|
{_activity, object} = ingest_data!(data)
|
||||||
|
|
||||||
|
assert object.data["emoji"]["cow"] == "https://fedidev.fun/static/cow.png"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -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
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
# 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.FedidevFunMissingAttributedToTest 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
|
||||||
|
|
||||||
|
test "inherits attributedTo from surrounding activity" do
|
||||||
|
data = File.read!("test/fixtures/fedidev.fun/missing_attributed_to.json") |> Jason.decode!()
|
||||||
|
ensure_remote_actor!(data["actor"])
|
||||||
|
|
||||||
|
assert {:ok, activity} = Transmogrifier.handle_incoming(data)
|
||||||
|
object = Object.normalize(activity.data["object"], fetch: false)
|
||||||
|
|
||||||
|
assert object.data["attributedTo"] == data["actor"]
|
||||||
|
assert object.data["actor"] == data["actor"]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
# 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.FedidevFunObjectTypeListTest 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 "accepts object type as a list and stores the first type" do
|
||||||
|
{_activity, object} = ingest_fixture!("test/fixtures/fedidev.fun/object_type_list.json")
|
||||||
|
assert object.data["type"] == "Note"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "selects a supported type after an extension type" do
|
||||||
|
data = File.read!("test/fixtures/fedidev.fun/object_type_list.json") |> Jason.decode!()
|
||||||
|
data = put_in(data, ["object", "type"], ["https://example.com/ns#CustomType", "Note"])
|
||||||
|
ensure_remote_actor!(data["actor"])
|
||||||
|
|
||||||
|
assert {:ok, activity} = Transmogrifier.handle_incoming(data)
|
||||||
|
object = Object.normalize(activity.data["object"], fetch: false)
|
||||||
|
|
||||||
|
assert object.data["type"] == "Note"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
# 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.FedidevFunQuoteUrlListTest 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 quoteUri list to the first object ID" do
|
||||||
|
{_activity, object} = ingest_fixture!("test/fixtures/fedidev.fun/quote_uri_list.json")
|
||||||
|
assert object.data["quoteUrl"] == "https://fedidev.fun/assets/note1.jsonap"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "normalizes quoteUrl list to the first object ID" do
|
||||||
|
{_activity, object} = ingest_fixture!("test/fixtures/fedidev.fun/quote_url_list.json")
|
||||||
|
assert object.data["quoteUrl"] == "https://fedidev.fun/assets/note1.jsonap"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "normalizes _misskey_quote list to the first object ID" do
|
||||||
|
{_activity, object} = ingest_fixture!("test/fixtures/fedidev.fun/misskey_quote_list.json")
|
||||||
|
assert object.data["quoteUrl"] == "https://fedidev.fun/assets/note1.jsonap"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,112 @@
|
||||||
|
# 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.FedidevFunTagInferenceTest 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 "infers Hashtag type when missing" do
|
||||||
|
{_activity, object} =
|
||||||
|
ingest_fixture!("test/fixtures/fedidev.fun/tag_hashtag_missing_type.json")
|
||||||
|
|
||||||
|
assert "test" in Object.hashtags(object)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "infers Mention type when missing" do
|
||||||
|
{_activity, object} =
|
||||||
|
ingest_fixture!("test/fixtures/fedidev.fun/tag_mention_missing_type.json")
|
||||||
|
|
||||||
|
assert Enum.any?(object.data["tag"], fn
|
||||||
|
%{"type" => "Mention", "href" => "https://remote.example/users/bob"} -> true
|
||||||
|
_ -> false
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "normalizes a list-typed tag type" do
|
||||||
|
data =
|
||||||
|
File.read!("test/fixtures/fedidev.fun/tag_hashtag_missing_type.json") |> Jason.decode!()
|
||||||
|
|
||||||
|
data = put_in(data, ["object", "tag", Access.at(0), "type"], ["Hashtag"])
|
||||||
|
ensure_remote_actor!(data["actor"])
|
||||||
|
|
||||||
|
assert {:ok, activity} = Transmogrifier.handle_incoming(data)
|
||||||
|
object = Object.normalize(activity.data["object"], fetch: false)
|
||||||
|
|
||||||
|
assert "test" in Object.hashtags(object)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "ignores a list-typed Hashtag without a string name" do
|
||||||
|
data =
|
||||||
|
File.read!("test/fixtures/fedidev.fun/tag_hashtag_missing_type.json") |> Jason.decode!()
|
||||||
|
|
||||||
|
data = put_in(data, ["object", "tag", Access.at(0), "type"], ["Hashtag"])
|
||||||
|
data = put_in(data, ["object", "tag", Access.at(0), "name"], nil)
|
||||||
|
ensure_remote_actor!(data["actor"])
|
||||||
|
|
||||||
|
assert {:ok, activity} = Transmogrifier.handle_incoming(data)
|
||||||
|
object = Object.normalize(activity.data["object"], fetch: false)
|
||||||
|
|
||||||
|
assert Object.hashtags(object) == []
|
||||||
|
end
|
||||||
|
|
||||||
|
test "infers a Link before deriving quoteUrl" do
|
||||||
|
data =
|
||||||
|
File.read!("test/fixtures/fedidev.fun/tag_hashtag_missing_type.json") |> Jason.decode!()
|
||||||
|
|
||||||
|
data =
|
||||||
|
put_in(data, ["object", "tag"], [
|
||||||
|
%{
|
||||||
|
"href" => "https://fedidev.fun/assets/note1.jsonap",
|
||||||
|
"mediaType" => "application/activity+json"
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
ensure_remote_actor!(data["actor"])
|
||||||
|
|
||||||
|
assert {:ok, activity} = Transmogrifier.handle_incoming(data)
|
||||||
|
object = Object.normalize(activity.data["object"], fetch: false)
|
||||||
|
|
||||||
|
assert object.data["quoteUrl"] == "https://fedidev.fun/assets/note1.jsonap"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
# 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.FedidevFunUrlListTest 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 object url list to a string" do
|
||||||
|
{_activity, object} = ingest_fixture!("test/fixtures/fedidev.fun/url_list.json")
|
||||||
|
assert object.data["url"] == "https://fedidev.fun/objects/123"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -572,11 +572,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.NoteHandlingTest do
|
||||||
Map.put(data["object"], "inReplyTo", ["https://shitposter.club/notice/2827873"])
|
Map.put(data["object"], "inReplyTo", ["https://shitposter.club/notice/2827873"])
|
||||||
|
|
||||||
modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
|
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", [])
|
object_with_reply = Map.put(data["object"], "inReplyTo", [])
|
||||||
modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
|
modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
|
||||||
assert modified_object["inReplyTo"] == []
|
assert modified_object["inReplyTo"] == nil
|
||||||
end
|
end
|
||||||
|
|
||||||
test "returns modified object when allowed incoming reply", %{data: data} do
|
test "returns modified object when allowed incoming reply", %{data: data} do
|
||||||
|
|
@ -671,13 +671,21 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.NoteHandlingTest do
|
||||||
test "returns object with emoji when object contains list tags" do
|
test "returns object with emoji when object contains list tags" do
|
||||||
assert Transmogrifier.fix_emoji(%{
|
assert Transmogrifier.fix_emoji(%{
|
||||||
"tag" => [
|
"tag" => [
|
||||||
%{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}},
|
%{
|
||||||
|
"type" => "Emoji",
|
||||||
|
"name" => ":bib:",
|
||||||
|
"icon" => %{"url" => "https://example.com/test"}
|
||||||
|
},
|
||||||
%{"type" => "Hashtag"}
|
%{"type" => "Hashtag"}
|
||||||
]
|
]
|
||||||
}) == %{
|
}) == %{
|
||||||
"emoji" => %{"bib" => "/test"},
|
"emoji" => %{"bib" => "https://example.com/test"},
|
||||||
"tag" => [
|
"tag" => [
|
||||||
%{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"},
|
%{
|
||||||
|
"icon" => %{"url" => "https://example.com/test"},
|
||||||
|
"name" => ":bib:",
|
||||||
|
"type" => "Emoji"
|
||||||
|
},
|
||||||
%{"type" => "Hashtag"}
|
%{"type" => "Hashtag"}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -685,10 +693,28 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.NoteHandlingTest do
|
||||||
|
|
||||||
test "returns object with emoji when object contains map tag" do
|
test "returns object with emoji when object contains map tag" do
|
||||||
assert Transmogrifier.fix_emoji(%{
|
assert Transmogrifier.fix_emoji(%{
|
||||||
"tag" => %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}}
|
"tag" => %{
|
||||||
|
"type" => "Emoji",
|
||||||
|
"name" => ":bib:",
|
||||||
|
"icon" => %{"url" => "https://example.com/test"}
|
||||||
|
}
|
||||||
}) == %{
|
}) == %{
|
||||||
"emoji" => %{"bib" => "/test"},
|
"emoji" => %{"bib" => "https://example.com/test"},
|
||||||
"tag" => %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"}
|
"tag" => %{
|
||||||
|
"icon" => %{"url" => "https://example.com/test"},
|
||||||
|
"name" => ":bib:",
|
||||||
|
"type" => "Emoji"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "fix_tag/1" do
|
||||||
|
test "ignores malformed Hashtag names and tag entries" do
|
||||||
|
tag = %{"type" => "Hashtag", "name" => 123}
|
||||||
|
|
||||||
|
assert Transmogrifier.fix_tag(%{"tag" => [tag, "malformed"]}) == %{
|
||||||
|
"tag" => [tag, "malformed"]
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,25 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.UserUpdateHandlingTest do
|
||||||
assert user.bio == "<p>Some bio</p>"
|
assert user.bio == "<p>Some bio</p>"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it selects an actor type after an extension type" do
|
||||||
|
user = insert(:user, local: false)
|
||||||
|
update_data = File.read!("test/fixtures/mastodon-update.json") |> Jason.decode!()
|
||||||
|
|
||||||
|
object =
|
||||||
|
update_data["object"]
|
||||||
|
|> Map.put("actor", user.ap_id)
|
||||||
|
|> Map.put("id", user.ap_id)
|
||||||
|
|> Map.put("type", ["https://example.com/ns#CustomActor", "Person"])
|
||||||
|
|
||||||
|
update_data =
|
||||||
|
update_data
|
||||||
|
|> Map.put("actor", user.ap_id)
|
||||||
|
|> Map.put("object", object)
|
||||||
|
|
||||||
|
assert {:ok, %Activity{local: false}} = Transmogrifier.handle_incoming(update_data)
|
||||||
|
assert User.get_cached_by_ap_id(user.ap_id).name == "gargle"
|
||||||
|
end
|
||||||
|
|
||||||
test "it works with alsoKnownAs" do
|
test "it works with alsoKnownAs" do
|
||||||
%{ap_id: actor} = insert(:user, local: false)
|
%{ap_id: actor} = insert(:user, local: false)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue