diff --git a/changelog.d/emoji_likes.add b/changelog.d/emoji_likes.add new file mode 100644 index 000000000..13c91a950 --- /dev/null +++ b/changelog.d/emoji_likes.add @@ -0,0 +1 @@ +Support Mitra-style emoji likes. diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 1e6ee7dc8..6517f5eff 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -492,6 +492,19 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do } # Rewrite misskey likes into EmojiReacts + defp handle_incoming_normalized( + %{ + "type" => "Like", + "content" => content + } = data, + options + ) + when is_binary(content) do + data + |> Map.put("type", "EmojiReact") + |> handle_incoming_normalized(options) + end + defp handle_incoming_normalized( %{ "type" => "Like", @@ -500,7 +513,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do options ) do data - |> Map.put("type", "EmojiReact") |> Map.put("content", @misskey_reactions[reaction] || reaction) |> handle_incoming_normalized(options) end diff --git a/lib/pleroma/web/federator.ex b/lib/pleroma/web/federator.ex index 58260afa8..676fc5137 100644 --- a/lib/pleroma/web/federator.ex +++ b/lib/pleroma/web/federator.ex @@ -122,6 +122,10 @@ defmodule Pleroma.Web.Federator do Logger.debug("Unhandled actor #{actor}, #{inspect(e)}") {:error, e} + {:reject, reason} = e -> + Logger.debug("Rejected by MRF: #{inspect(reason)}") + {:error, e} + e -> # Just drop those for now Logger.debug(fn -> "Unhandled activity\n" <> Jason.encode!(params, pretty: true) end) diff --git a/test/fixtures/misskey-custom-emoji-like.json b/test/fixtures/misskey-custom-emoji-like.json new file mode 100644 index 000000000..51a825d42 --- /dev/null +++ b/test/fixtures/misskey-custom-emoji-like.json @@ -0,0 +1,54 @@ + { + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + "Emoji": "toot:Emoji", + "Hashtag": "as:Hashtag", + "PropertyValue": "schema:PropertyValue", + "_misskey_content": "misskey:_misskey_content", + "_misskey_quote": "misskey:_misskey_quote", + "_misskey_reaction": "misskey:_misskey_reaction", + "_misskey_summary": "misskey:_misskey_summary", + "_misskey_votes": "misskey:_misskey_votes", + "backgroundUrl": "sharkey:backgroundUrl", + "discoverable": "toot:discoverable", + "featured": "toot:featured", + "fedibird": "http://fedibird.com/ns#", + "firefish": "https://joinfirefish.org/ns#", + "isCat": "misskey:isCat", + "listenbrainz": "sharkey:listenbrainz", + "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", + "misskey": "https://misskey-hub.net/ns#", + "quoteUri": "fedibird:quoteUri", + "quoteUrl": "as:quoteUrl", + "schema": "http://schema.org#", + "sensitive": "as:sensitive", + "sharkey": "https://joinsharkey.org/ns#", + "speakAsCat": "firefish:speakAsCat", + "toot": "http://joinmastodon.org/ns#", + "value": "schema:value", + "vcard": "http://www.w3.org/2006/vcard/ns#" + } + ], + "_misskey_reaction": ":blobwtfnotlikethis:", + "actor": "https://mai.waifuism.life/users/9otxaeemjqy70001", + "content": ":blobwtfnotlikethis:", + "id": "https://mai.waifuism.life/likes/9q2xifhrdnb0001b", + "object": "https://bungle.online/notes/9q2xi2sy4k", + "tag": [ + { + "icon": { + "mediaType": "image/png", + "type": "Image", + "url": "https://mai.waifuism.life/files/1b0510f2-1fb4-43f5-a399-10053bbd8f0f" + }, + "id": "https://mai.waifuism.life/emojis/blobwtfnotlikethis", + "name": ":blobwtfnotlikethis:", + "type": "Emoji", + "updated": "2024-02-07T02:21:46.497Z" + } + ], + "type": "Like" +} + diff --git a/test/fixtures/mitra-custom-emoji-like.json b/test/fixtures/mitra-custom-emoji-like.json new file mode 100644 index 000000000..4d727febd --- /dev/null +++ b/test/fixtures/mitra-custom-emoji-like.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + "https://w3id.org/security/data-integrity/v1", + { + "Emoji": "toot:Emoji", + "Hashtag": "as:Hashtag", + "sensitive": "as:sensitive", + "toot": "http://joinmastodon.org/ns#" + } + ], + "actor": "https://mitra.social/users/silverpill", + "cc": [], + "content": ":ablobcatheartsqueeze:", + "id": "https://mitra.social/activities/like/0195a89a-a3a0-ead4-3a1c-aa6311397cfd", + "object": "https://framapiaf.org/users/peertube/statuses/114182703352270287", + "proof": { + "created": "2025-03-18T09:34:21.610678375Z", + "cryptosuite": "eddsa-jcs-2022", + "proofPurpose": "assertionMethod", + "proofValue": "z5AvpwkXQGFpTneRVDNeF48Jo9qYG6PgrE5HaPPpQNdNyc31ULMN4Vxd4aFXELo4Rk5Y9hd9nDy254xP8v5uGGWp1", + "type": "DataIntegrityProof", + "verificationMethod": "https://mitra.social/users/silverpill#ed25519-key" + }, + "tag": [ + { + "attributedTo": "https://mitra.social/actor", + "icon": { + "mediaType": "image/png", + "type": "Image", + "url": "https://mitra.social/media/a08e153441b25e512ab1b2e8922f5d8cd928322c8b79958cd48297ac722a4117.png" + }, + "id": "https://mitra.social/objects/emojis/ablobcatheartsqueeze", + "name": ":ablobcatheartsqueeze:", + "type": "Emoji", + "updated": "1970-01-01T00:00:00Z" + } + ], + "to": [ + "https://framapiaf.org/users/peertube", + "https://www.w3.org/ns/activitystreams#Public" + ], + "type": "Like" +} + diff --git a/test/pleroma/web/activity_pub/transmogrifier/like_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/like_handling_test.exs index c02f66d77..fc04c1391 100644 --- a/test/pleroma/web/activity_pub/transmogrifier/like_handling_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/like_handling_test.exs @@ -6,6 +6,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.LikeHandlingTest do use Pleroma.DataCase, async: true alias Pleroma.Activity + alias Pleroma.Object alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.CommonAPI @@ -75,4 +76,71 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.LikeHandlingTest do assert activity_data["object"] == activity.data["object"] assert activity_data["content"] == "⭐" end + + test "it works for misskey likes with custom emoji" do + user = insert(:user) + + {:ok, activity} = CommonAPI.post(user, %{status: "hello"}) + + data = + File.read!("test/fixtures/misskey-custom-emoji-like.json") + |> Jason.decode!() + |> Map.put("object", activity.data["object"]) + + _actor = insert(:user, ap_id: data["actor"], local: false) + + {:ok, %Activity{data: activity_data, local: false}} = Transmogrifier.handle_incoming(data) + + assert activity_data["actor"] == data["actor"] + assert activity_data["type"] == "EmojiReact" + assert activity_data["id"] == data["id"] + assert activity_data["object"] == activity.data["object"] + assert activity_data["content"] == ":blobwtfnotlikethis:" + + assert [["blobwtfnotlikethis", _, _]] = + Object.get_by_ap_id(activity.data["object"]) + |> Object.get_emoji_reactions() + end + + test "it works for mitra likes with custom emoji" do + user = insert(:user) + + {:ok, activity} = CommonAPI.post(user, %{status: "hello"}) + + data = + File.read!("test/fixtures/mitra-custom-emoji-like.json") + |> Jason.decode!() + |> Map.put("object", activity.data["object"]) + + _actor = insert(:user, ap_id: data["actor"], local: false) + + {:ok, %Activity{data: activity_data, local: false}} = Transmogrifier.handle_incoming(data) + + assert activity_data["actor"] == data["actor"] + assert activity_data["type"] == "EmojiReact" + assert activity_data["id"] == data["id"] + assert activity_data["object"] == activity.data["object"] + assert activity_data["content"] == ":ablobcatheartsqueeze:" + + assert [["ablobcatheartsqueeze", _, _]] = + Object.get_by_ap_id(activity.data["object"]) + |> Object.get_emoji_reactions() + end + + test "it works for likes with wrong content" do + user = insert(:user) + + {:ok, activity} = CommonAPI.post(user, %{status: "hello"}) + + data = + File.read!("test/fixtures/mitra-custom-emoji-like.json") + |> Jason.decode!() + |> Map.put("object", activity.data["object"]) + |> Map.put("content", 1) + + _actor = insert(:user, ap_id: data["actor"], local: false) + + assert {:ok, activity} = Transmogrifier.handle_incoming(data) + assert activity.data["type"] == "Like" + end end