diff options
Diffstat (limited to 'lib/pleroma/web/activity_pub')
-rw-r--r-- | lib/pleroma/web/activity_pub/activity_pub.ex | 26 | ||||
-rw-r--r-- | lib/pleroma/web/activity_pub/transmogrifier.ex | 73 | ||||
-rw-r--r-- | lib/pleroma/web/activity_pub/utils.ex | 93 |
3 files changed, 190 insertions, 2 deletions
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 65dd251f3..d0c014e9d 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -322,6 +322,32 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end end + def react_with_emoji(user, object, emoji, options \\ []) do + with local <- Keyword.get(options, :local, true), + activity_id <- Keyword.get(options, :activity_id, nil), + Pleroma.Emoji.is_unicode_emoji?(emoji), + reaction_data <- make_emoji_reaction_data(user, object, emoji, activity_id), + {:ok, activity} <- insert(reaction_data, local), + {:ok, object} <- add_emoji_reaction_to_object(activity, object), + :ok <- maybe_federate(activity) do + {:ok, activity, object} + end + end + + def unreact_with_emoji(user, reaction_id, options \\ []) do + with local <- Keyword.get(options, :local, true), + activity_id <- Keyword.get(options, :activity_id, nil), + user_ap_id <- user.ap_id, + %Activity{actor: ^user_ap_id} = reaction_activity <- Activity.get_by_ap_id(reaction_id), + object <- Object.normalize(reaction_activity), + unreact_data <- make_undo_data(user, reaction_activity, activity_id), + {:ok, activity} <- insert(unreact_data, local), + {:ok, object} <- remove_emoji_reaction_from_object(reaction_activity, object), + :ok <- maybe_federate(activity) do + {:ok, activity, object} + end + end + # TODO: This is weird, maybe we shouldn't check here if we can make the activity. def like( %User{ap_id: ap_id} = user, diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 91a164eff..15612545b 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -566,6 +566,34 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end end + @misskey_reactions %{ + "like" => "👍", + "love" => "❤️", + "laugh" => "😆", + "hmm" => "🤔", + "surprise" => "😮", + "congrats" => "🎉", + "angry" => "💢", + "confused" => "😥", + "rip" => "😇", + "pudding" => "🍮", + "star" => "⭐" + } + + @doc "Rewrite misskey likes into EmojiReactions" + def handle_incoming( + %{ + "type" => "Like", + "_misskey_reaction" => reaction + } = data, + options + ) do + data + |> Map.put("type", "EmojiReaction") + |> Map.put("content", @misskey_reactions[reaction] || reaction) + |> handle_incoming(options) + end + def handle_incoming( %{"type" => "Like", "object" => object_id, "actor" => _actor, "id" => id} = data, _options @@ -581,6 +609,27 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end def handle_incoming( + %{ + "type" => "EmojiReaction", + "object" => object_id, + "actor" => _actor, + "id" => id, + "content" => emoji + } = data, + _options + ) do + with actor <- Containment.get_actor(data), + {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor), + {:ok, object} <- get_obj_helper(object_id), + {:ok, activity, _object} <- + ActivityPub.react_with_emoji(actor, object, emoji, activity_id: id, local: false) do + {:ok, activity} + else + _e -> :error + end + end + + def handle_incoming( %{"type" => "Announce", "object" => object_id, "actor" => _actor, "id" => id} = data, _options ) do @@ -718,6 +767,28 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def handle_incoming( %{ "type" => "Undo", + "object" => %{"type" => "EmojiReaction", "id" => reaction_activity_id}, + "actor" => _actor, + "id" => id + } = data, + _options + ) do + with actor <- Containment.get_actor(data), + {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor), + {:ok, activity, _} <- + ActivityPub.unreact_with_emoji(actor, reaction_activity_id, + activity_id: id, + local: false + ) do + {:ok, activity} + else + _e -> :error + end + end + + def handle_incoming( + %{ + "type" => "Undo", "object" => %{"type" => "Block", "object" => blocked}, "actor" => blocker, "id" => id @@ -1048,7 +1119,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do Map.put(object, "attachment", attachments) end - defp strip_internal_fields(object) do + def strip_internal_fields(object) do object |> Map.drop(Pleroma.Constants.object_internal_fields()) end diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index d812fd734..91cfa2194 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -255,6 +255,16 @@ defmodule Pleroma.Web.ActivityPub.Utils do |> Repo.one() end + @doc """ + Returns like activities targeting an object + """ + def get_object_likes(%{data: %{"id" => id}}) do + id + |> Activity.Queries.by_object_id() + |> Activity.Queries.by_type("Like") + |> Repo.all() + end + @spec make_like_data(User.t(), map(), String.t()) :: map() def make_like_data( %User{ap_id: ap_id} = actor, @@ -286,13 +296,30 @@ defmodule Pleroma.Web.ActivityPub.Utils do |> maybe_put("id", activity_id) end + def make_emoji_reaction_data(user, object, emoji, activity_id) do + make_like_data(user, object, activity_id) + |> Map.put("type", "EmojiReaction") + |> Map.put("content", emoji) + end + @spec update_element_in_object(String.t(), list(any), Object.t()) :: {:ok, Object.t()} | {:error, Ecto.Changeset.t()} def update_element_in_object(property, element, object) do + length = + if is_map(element) do + element + |> Map.values() + |> List.flatten() + |> length() + else + element + |> length() + end + data = Map.merge( object.data, - %{"#{property}_count" => length(element), "#{property}s" => element} + %{"#{property}_count" => length, "#{property}s" => element} ) object @@ -300,6 +327,38 @@ defmodule Pleroma.Web.ActivityPub.Utils do |> Object.update_and_set_cache() end + @spec add_emoji_reaction_to_object(Activity.t(), Object.t()) :: + {:ok, Object.t()} | {:error, Ecto.Changeset.t()} + + def add_emoji_reaction_to_object( + %Activity{data: %{"content" => emoji, "actor" => actor}}, + object + ) do + reactions = object.data["reactions"] || %{} + emoji_actors = reactions[emoji] || [] + new_emoji_actors = [actor | emoji_actors] |> Enum.uniq() + new_reactions = Map.put(reactions, emoji, new_emoji_actors) + update_element_in_object("reaction", new_reactions, object) + end + + def remove_emoji_reaction_from_object( + %Activity{data: %{"content" => emoji, "actor" => actor}}, + object + ) do + reactions = object.data["reactions"] || %{} + emoji_actors = reactions[emoji] || [] + new_emoji_actors = List.delete(emoji_actors, actor) + + new_reactions = + if new_emoji_actors == [] do + Map.delete(reactions, emoji) + else + Map.put(reactions, emoji, new_emoji_actors) + end + + update_element_in_object("reaction", new_reactions, object) + end + @spec add_like_to_object(Activity.t(), Object.t()) :: {:ok, Object.t()} | {:error, Ecto.Changeset.t()} def add_like_to_object(%Activity{data: %{"actor" => actor}}, object) do @@ -397,6 +456,19 @@ defmodule Pleroma.Web.ActivityPub.Utils do |> Repo.one() end + def get_latest_reaction(internal_activity_id, %{ap_id: ap_id}, emoji) do + %{data: %{"object" => object_ap_id}} = Activity.get_by_id(internal_activity_id) + + "EmojiReaction" + |> Activity.Queries.by_type() + |> where(actor: ^ap_id) + |> where([activity], fragment("?->>'content' = ?", activity.data, ^emoji)) + |> Activity.Queries.by_object_id(object_ap_id) + |> order_by([activity], fragment("? desc nulls last", activity.id)) + |> limit(1) + |> Repo.one() + end + #### Announce-related helpers @doc """ @@ -489,6 +561,25 @@ defmodule Pleroma.Web.ActivityPub.Utils do |> maybe_put("id", activity_id) end + def make_undo_data( + %User{ap_id: actor, follower_address: follower_address}, + %Activity{ + data: %{"id" => undone_activity_id, "context" => context}, + actor: undone_activity_actor + }, + activity_id \\ nil + ) do + %{ + "type" => "Undo", + "actor" => actor, + "object" => undone_activity_id, + "to" => [follower_address, undone_activity_actor], + "cc" => [Pleroma.Constants.as_public()], + "context" => context + } + |> maybe_put("id", activity_id) + end + @spec add_announce_to_object(Activity.t(), Object.t()) :: {:ok, Object.t()} | {:error, Ecto.Changeset.t()} def add_announce_to_object( |