diff --git a/changelog.d/fedidev-federation-compat.add b/changelog.d/fedidev-federation-compat.add
new file mode 100644
index 000000000..bb29925f9
--- /dev/null
+++ b/changelog.d/fedidev-federation-compat.add
@@ -0,0 +1 @@
+Accept more real-world ActivityPub object shapes collected by fedidev.fun
diff --git a/lib/pleroma/object/containment.ex b/lib/pleroma/object/containment.ex
index 77fac12c0..c4cc1f014 100644
--- a/lib/pleroma/object/containment.ex
+++ b/lib/pleroma/object/containment.ex
@@ -15,12 +15,28 @@ defmodule Pleroma.Object.Containment do
actor
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
- if is_binary(Enum.at(actor, 0)) do
- Enum.at(actor, 0)
- else
- Enum.find(actor, fn %{"type" => type} -> type in ["Person", "Service", "Application"] end)
- |> Map.get("id")
+ case actor do
+ [first | _] when is_binary(first) ->
+ first
+
+ _ ->
+ Enum.find_value(actor, fn
+ %{"type" => type, "id" => id}
+ when type in ["Person", "Service", "Application"] and is_binary(id) ->
+ id
+
+ _ ->
+ nil
+ end)
end
end
@@ -28,10 +44,6 @@ defmodule Pleroma.Object.Containment do
id
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
id
end
@@ -67,9 +79,11 @@ defmodule Pleroma.Object.Containment do
def contain_origin(id, %{"actor" => _actor} = params) do
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
def contain_origin(id, %{"attributedTo" => actor} = params),
diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex
index 17652a0de..0254d6dc1 100644
--- a/lib/pleroma/web/activity_pub/object_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validator.ex
@@ -118,6 +118,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
when objtype in ~w[Question Answer Audio Video Image Event Article Note Page] do
with {:ok, object_data} <-
object
+ |> CommonFixes.maybe_set_attributed_to_from_activity(create_activity)
|> CommonFixes.maybe_add_language_from_activity(create_activity)
|> cast_and_apply_and_stringify_with_history(),
meta = Keyword.put(meta, :object_data, object_data),
@@ -172,6 +173,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
{_, {:ok, object_data, _}} <-
{:object_validation,
object
+ |> CommonFixes.maybe_set_attributed_to_from_activity(update_activity)
|> CommonFixes.maybe_add_language_from_activity(update_activity)
|> validate(meta)},
meta = Keyword.put(meta, :object_data, object_data),
diff --git a/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex b/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex
index 844ad2c8f..1d6e0abfb 100644
--- a/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex
@@ -50,7 +50,32 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do
end
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_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")
- def fix_attachments(%{"attachment" => attachment} = data) when is_map(attachment),
- do: Map.put(data, "attachment", [attachment])
-
- def fix_attachments(data), do: data
+ def fix_attachments(data), do: Transmogrifier.fix_attachments(data)
defp remote_mention_resolver(
%{"id" => ap_id, "tag" => tags},
diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex
index 2e0f7a8a0..c5defd6f4 100644
--- a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex
@@ -41,22 +41,34 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do
def fix_object_defaults(data) do
data = Maps.filter_empty_values(data)
+ in_reply_to_id = in_reply_to_id(data["inReplyTo"])
+
context =
Utils.maybe_create_context(
- data["context"] || data["conversation"] || data["inReplyTo"] || data["id"]
+ data["context"] || data["conversation"] || in_reply_to_id || data["id"]
)
- %User{follower_address: follower_collection} = User.get_cached_by_ap_id(data["attributedTo"])
+ data = Map.put(data, "context", context)
- data
- |> Map.put("context", context)
- |> cast_and_filter_recipients("to", follower_collection)
- |> cast_and_filter_recipients("cc", follower_collection)
- |> cast_and_filter_recipients("bto", follower_collection)
- |> cast_and_filter_recipients("bcc", follower_collection)
- |> Transmogrifier.fix_implicit_addressing(follower_collection)
+ with attributed_to when is_binary(attributed_to) <- data["attributedTo"],
+ %User{follower_address: follower_collection} <- User.get_cached_by_ap_id(attributed_to) do
+ data
+ |> cast_and_filter_recipients("to", follower_collection)
+ |> cast_and_filter_recipients("cc", follower_collection)
+ |> cast_and_filter_recipients("bto", follower_collection)
+ |> cast_and_filter_recipients("bcc", follower_collection)
+ |> Transmogrifier.fix_implicit_addressing(follower_collection)
+ else
+ _ -> data
+ end
end
+ defp in_reply_to_id(in_reply_to) when is_binary(in_reply_to), do: in_reply_to
+ defp in_reply_to_id(%{"id" => in_reply_to}) when is_binary(in_reply_to), do: in_reply_to
+ defp in_reply_to_id(%{"href" => in_reply_to}) when is_binary(in_reply_to), do: in_reply_to
+ defp in_reply_to_id([in_reply_to | _]), do: in_reply_to_id(in_reply_to)
+ defp in_reply_to_id(_), do: nil
+
def fix_activity_addressing(activity) do
%User{follower_address: follower_collection} = User.get_cached_by_ap_id(activity["actor"])
@@ -79,6 +91,17 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do
|> Map.put("attributedTo", actor)
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
data
|> Map.put("context", object_context)
@@ -96,23 +119,37 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do
Map.put(data, "to", to)
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
# https://github.com/fedibird/mastodon/commit/dbd7ae6cf58a92ec67c512296b4daaea0d01e6ac
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
# Old Fedibird (bug)
# https://github.com/fedibird/mastodon/issues/9
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
# Misskey fallback
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
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
+ 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`,
# not a list of users.
# https://github.com/mastodon/mastodon/pull/32007
diff --git a/lib/pleroma/web/activity_pub/object_validators/tag_validator.ex b/lib/pleroma/web/activity_pub/object_validators/tag_validator.ex
index dc2770189..0659c93d7 100644
--- a/lib/pleroma/web/activity_pub/object_validators/tag_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/tag_validator.ex
@@ -12,6 +12,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.TagValidator do
require Pleroma.Constants
@primary_key false
+ @tag_types ~w[Mention Hashtag Emoji Link]
+
embedded_schema do
# Common
field(:type, :string)
@@ -49,7 +51,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.TagValidator do
|> validate_required([:type, :href])
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)
data = Map.put(data, "name", name)
@@ -58,13 +60,42 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.TagValidator do
|> validate_required([:type, :name])
end
- def changeset(struct, %{"type" => "Emoji"} = data) do
- data = Map.put(data, "name", String.trim(data["name"], ":"))
-
+ def changeset(struct, %{"type" => "Hashtag"} = data) do
struct
- |> cast(data, [:type, :name, :updated, :id])
- |> cast_embed(:icon, with: &icon_changeset/2)
- |> validate_required([:type, :name, :icon])
+ |> cast(data, [])
+ |> Map.put(:action, :ignore)
+ 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
def changeset(struct, %{"type" => "Link"} = data) do
@@ -74,12 +105,80 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.TagValidator do
|> validate_required([:type, :href, :mediaType])
end
+ def changeset(struct, %{"type" => types} = data) when is_list(types) do
+ changeset(struct, infer_type(data))
+ end
+
def changeset(struct, %{"type" => _} = data) do
struct
|> cast(data, [])
|> Map.put(:action, :ignore)
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
struct
|> cast(data, [:type, :url])
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index a013dd226..33ab62ca3 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -19,6 +19,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
alias Pleroma.Web.ActivityPub.Builder
alias Pleroma.Web.ActivityPub.ObjectValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
+ alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator
alias Pleroma.Web.ActivityPub.Pipeline
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility
@@ -28,6 +29,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
require Pleroma.Constants
+ @supported_types Pleroma.Constants.activity_types() ++
+ Pleroma.Constants.object_types() ++ Pleroma.Constants.actor_types()
+
@doc """
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)
when not is_nil(in_reply_to) do
in_reply_to_id = prepare_in_reply_to(in_reply_to)
+
+ object =
+ cond do
+ is_list(in_reply_to) ->
+ if not_empty_string(in_reply_to_id) do
+ Map.put(object, "inReplyTo", in_reply_to_id)
+ else
+ Map.delete(object, "inReplyTo")
+ end
+
+ is_map(in_reply_to) and is_nil(in_reply_to["id"]) and is_binary(in_reply_to["href"]) ->
+ Map.put(object, "inReplyTo", in_reply_to["href"])
+
+ is_map(in_reply_to) and is_nil(in_reply_to["id"]) and is_nil(in_reply_to["href"]) ->
+ Map.delete(object, "inReplyTo")
+
+ true ->
+ object
+ end
+
depth = (options[:depth] || 0) + 1
- if Federator.allowed_thread_distance?(depth) do
+ if not_empty_string(in_reply_to_id) and Federator.allowed_thread_distance?(depth) do
with {:ok, replied_object} <- get_obj_helper(in_reply_to_id, options),
%Activity{} <- Activity.get_create_by_object_ap_id(replied_object.data["id"]) do
object
@@ -249,9 +273,17 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
is_map(in_reply_to) && is_bitstring(in_reply_to["id"]) ->
in_reply_to["id"]
+ is_map(in_reply_to) && is_bitstring(in_reply_to["href"]) ->
+ in_reply_to["href"]
+
is_list(in_reply_to) && is_bitstring(Enum.at(in_reply_to, 0)) ->
Enum.at(in_reply_to, 0)
+ is_list(in_reply_to) && is_map(Enum.at(in_reply_to, 0)) ->
+ in_reply_to
+ |> Enum.at(0)
+ |> prepare_in_reply_to()
+
true ->
""
end
@@ -265,59 +297,74 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> Map.drop(["conversation"])
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
attachments =
- Enum.map(attachment, fn data ->
- url =
- cond do
- is_list(data["url"]) -> List.first(data["url"])
- is_map(data["url"]) -> data["url"]
- true -> nil
- end
+ Enum.map(attachment, fn
+ data when is_map(data) ->
+ url =
+ cond do
+ is_list(data["url"]) -> List.first(data["url"])
+ is_map(data["url"]) -> data["url"]
+ true -> nil
+ end
- media_type =
- cond do
- is_map(url) && url =~ Pleroma.Constants.mime_regex() ->
- url["mediaType"]
+ url_data = if is_map(url), do: url, else: %{}
- is_bitstring(data["mediaType"]) && data["mediaType"] =~ Pleroma.Constants.mime_regex() ->
- data["mediaType"]
+ media_type =
+ 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() ->
- data["mimeType"]
+ is_bitstring(data["mediaType"]) &&
+ data["mediaType"] =~ Pleroma.Constants.mime_regex() ->
+ data["mediaType"]
- true ->
- nil
- end
+ is_bitstring(data["mimeType"]) && data["mimeType"] =~ Pleroma.Constants.mime_regex() ->
+ data["mimeType"]
- href =
- cond do
- is_map(url) && is_binary(url["href"]) -> url["href"]
- is_binary(data["url"]) -> data["url"]
- is_binary(data["href"]) -> data["href"]
- true -> nil
- end
+ true ->
+ nil
+ end
+
+ href =
+ cond do
+ 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,
- "type" => Map.get(url || %{}, "type", "Link")
+ "url" => [attachment_url],
+ "type" => data["type"] || "Document"
}
|> Maps.put_if_present("mediaType", media_type)
- |> Maps.put_if_present("width", (url || %{})["width"] || data["width"])
- |> Maps.put_if_present("height", (url || %{})["height"] || data["height"])
+ |> Maps.put_if_present("name", data["name"])
+ |> 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
- end
end)
|> Enum.filter(& &1)
@@ -351,24 +398,58 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
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
emoji =
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 ->
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)
Map.put(object, "emoji", emoji)
end
- def fix_emoji(%{"tag" => %{"type" => "Emoji"} = tag} = object) do
- name = String.trim(tag["name"], ":")
- emoji = %{name => tag["icon"]["url"]}
+ def fix_emoji(%{"tag" => %{"type" => "Emoji", "name" => name} = tag} = object)
+ when is_binary(name) do
+ 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
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
tags =
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
%{"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
data
|> fix_recursive(&strip_internal_fields/1)
+ |> fix_recursive(&fix_type_as_string/1)
|> handle_incoming_normalized(options)
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
# with nil ID.
defp handle_incoming_normalized(
diff --git a/test/fixtures/fedidev.fun/attachment_embedded_note.json b/test/fixtures/fedidev.fun/attachment_embedded_note.json
new file mode 100644
index 000000000..8e83762dc
--- /dev/null
+++ b/test/fixtures/fedidev.fun/attachment_embedded_note.json
@@ -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"
+ }
+ }
+}
+
diff --git a/test/fixtures/fedidev.fun/attachment_payto_link.json b/test/fixtures/fedidev.fun/attachment_payto_link.json
new file mode 100644
index 000000000..1da324cb3
--- /dev/null
+++ b/test/fixtures/fedidev.fun/attachment_payto_link.json
@@ -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"
+ }
+ }
+}
+
diff --git a/test/fixtures/fedidev.fun/attachment_url_list.json b/test/fixtures/fedidev.fun/attachment_url_list.json
new file mode 100644
index 000000000..0efaeafdb
--- /dev/null
+++ b/test/fixtures/fedidev.fun/attachment_url_list.json
@@ -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"
+ }
+ ]
+ }
+ }
+}
+
diff --git a/test/fixtures/fedidev.fun/emoji_icon_string.json b/test/fixtures/fedidev.fun/emoji_icon_string.json
new file mode 100644
index 000000000..0be3deb8c
--- /dev/null
+++ b/test/fixtures/fedidev.fun/emoji_icon_string.json
@@ -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"
+ }
+ ]
+ }
+}
+
diff --git a/test/fixtures/fedidev.fun/emoji_icon_url_missing_type.json b/test/fixtures/fedidev.fun/emoji_icon_url_missing_type.json
new file mode 100644
index 000000000..2837872eb
--- /dev/null
+++ b/test/fixtures/fedidev.fun/emoji_icon_url_missing_type.json
@@ -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"
+ }
+ }
+ ]
+ }
+}
+
diff --git a/test/fixtures/fedidev.fun/emoji_missing_icon.json b/test/fixtures/fedidev.fun/emoji_missing_icon.json
new file mode 100644
index 000000000..69af01a73
--- /dev/null
+++ b/test/fixtures/fedidev.fun/emoji_missing_icon.json
@@ -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:"
+ }
+ ]
+ }
+}
+
diff --git a/test/fixtures/fedidev.fun/in_reply_to_collection.json b/test/fixtures/fedidev.fun/in_reply_to_collection.json
new file mode 100644
index 000000000..4deb7ed9a
--- /dev/null
+++ b/test/fixtures/fedidev.fun/in_reply_to_collection.json
@@ -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"
+ ]
+ }
+ }
+}
diff --git a/test/fixtures/fedidev.fun/in_reply_to_embedded_note.json b/test/fixtures/fedidev.fun/in_reply_to_embedded_note.json
new file mode 100644
index 000000000..de66f144f
--- /dev/null
+++ b/test/fixtures/fedidev.fun/in_reply_to_embedded_note.json
@@ -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"
+ }
+ }
+}
diff --git a/test/fixtures/fedidev.fun/in_reply_to_link.json b/test/fixtures/fedidev.fun/in_reply_to_link.json
new file mode 100644
index 000000000..f000ddcac
--- /dev/null
+++ b/test/fixtures/fedidev.fun/in_reply_to_link.json
@@ -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"
+ }
+ }
+}
diff --git a/test/fixtures/fedidev.fun/in_reply_to_list.json b/test/fixtures/fedidev.fun/in_reply_to_list.json
new file mode 100644
index 000000000..7242558b3
--- /dev/null
+++ b/test/fixtures/fedidev.fun/in_reply_to_list.json
@@ -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"
+ ]
+ }
+}
diff --git a/test/fixtures/fedidev.fun/missing_attributed_to.json b/test/fixtures/fedidev.fun/missing_attributed_to.json
new file mode 100644
index 000000000..c2e644e84
--- /dev/null
+++ b/test/fixtures/fedidev.fun/missing_attributed_to.json
@@ -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"
+ }
+}
+
diff --git a/test/fixtures/fedidev.fun/misskey_quote_list.json b/test/fixtures/fedidev.fun/misskey_quote_list.json
new file mode 100644
index 000000000..b9ea08691
--- /dev/null
+++ b/test/fixtures/fedidev.fun/misskey_quote_list.json
@@ -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"
+ ]
+ }
+}
diff --git a/test/fixtures/fedidev.fun/object_type_list.json b/test/fixtures/fedidev.fun/object_type_list.json
new file mode 100644
index 000000000..f4ab00578
--- /dev/null
+++ b/test/fixtures/fedidev.fun/object_type_list.json
@@ -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"
+ }
+}
diff --git a/test/fixtures/fedidev.fun/quote_uri_list.json b/test/fixtures/fedidev.fun/quote_uri_list.json
new file mode 100644
index 000000000..9245077e6
--- /dev/null
+++ b/test/fixtures/fedidev.fun/quote_uri_list.json
@@ -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"
+ ]
+ }
+}
diff --git a/test/fixtures/fedidev.fun/quote_url_list.json b/test/fixtures/fedidev.fun/quote_url_list.json
new file mode 100644
index 000000000..eecd0dd35
--- /dev/null
+++ b/test/fixtures/fedidev.fun/quote_url_list.json
@@ -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"
+ ]
+ }
+}
diff --git a/test/fixtures/fedidev.fun/tag_hashtag_missing_type.json b/test/fixtures/fedidev.fun/tag_hashtag_missing_type.json
new file mode 100644
index 000000000..74f9d91b8
--- /dev/null
+++ b/test/fixtures/fedidev.fun/tag_hashtag_missing_type.json
@@ -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"
+ }
+ ]
+ }
+}
+
diff --git a/test/fixtures/fedidev.fun/tag_mention_missing_type.json b/test/fixtures/fedidev.fun/tag_mention_missing_type.json
new file mode 100644
index 000000000..d290be6c2
--- /dev/null
+++ b/test/fixtures/fedidev.fun/tag_mention_missing_type.json
@@ -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"
+ }
+ ]
+ }
+}
+
diff --git a/test/fixtures/fedidev.fun/url_list.json b/test/fixtures/fedidev.fun/url_list.json
new file mode 100644
index 000000000..b4cfd4cd6
--- /dev/null
+++ b/test/fixtures/fedidev.fun/url_list.json
@@ -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"
+ ]
+ }
+}
diff --git a/test/pleroma/object/containment_test.exs b/test/pleroma/object/containment_test.exs
index cf906ef47..5560dfce5 100644
--- a/test/pleroma/object/containment_test.exs
+++ b/test/pleroma/object/containment_test.exs
@@ -27,6 +27,11 @@ defmodule Pleroma.Object.ContainmentTest do
})
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
data = %{
"id" => "http://example.com/~alyssa/activities/1234.json"
diff --git a/test/pleroma/web/activity_pub/transmogrifier/fedidev_fun_attachments_test.exs b/test/pleroma/web/activity_pub/transmogrifier/fedidev_fun_attachments_test.exs
new file mode 100644
index 000000000..9d15974fc
--- /dev/null
+++ b/test/pleroma/web/activity_pub/transmogrifier/fedidev_fun_attachments_test.exs
@@ -0,0 +1,98 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors
Some bio
" 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 %{ap_id: actor} = insert(:user, local: false)