Merge remote-tracking branch 'origin/develop' into reactions
[akkoma] / lib / pleroma / web / activity_pub / transmogrifier.ex
index 18a3c3f39f529e5cd53ebdc9f1240af9336f8b64..ea209b5ea1b73e81d23de17e4726fd422a420c30 100644 (file)
@@ -15,6 +15,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   alias Pleroma.Web.ActivityPub.Utils
   alias Pleroma.Web.ActivityPub.Visibility
   alias Pleroma.Web.Federator
+  alias Pleroma.Workers.TransmogrifierWorker
 
   import Ecto.Query
 
@@ -165,7 +166,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
         |> Map.put("context", replied_object.data["context"] || object["conversation"])
       else
         e ->
-          Logger.error("Couldn't fetch \"#{inspect(in_reply_to_id)}\", error: #{inspect(e)}")
+          Logger.error("Couldn't fetch #{inspect(in_reply_to_id)}, error: #{inspect(e)}")
           object
       end
     else
@@ -429,6 +430,36 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     end
   end
 
+  def handle_incoming(
+        %{"type" => "Listen", "object" => %{"type" => "Audio"} = object} = data,
+        options
+      ) do
+    actor = Containment.get_actor(data)
+
+    data =
+      Map.put(data, "actor", actor)
+      |> fix_addressing
+
+    with {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(data["actor"]) do
+      options = Keyword.put(options, :depth, (options[:depth] || 0) + 1)
+      object = fix_object(object, options)
+
+      params = %{
+        to: data["to"],
+        object: object,
+        actor: user,
+        context: nil,
+        local: false,
+        published: data["published"],
+        additional: Map.take(data, ["cc", "id"])
+      }
+
+      ActivityPub.listen(params)
+    else
+      _e -> :error
+    end
+  end
+
   def handle_incoming(
         %{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data,
         _options
@@ -543,6 +574,27 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     end
   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
@@ -669,6 +721,28 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     end
   end
 
+  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",
@@ -722,6 +796,24 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     end
   end
 
+  # For Undos that don't have the complete object attached, try to find it in our database.
+  def handle_incoming(
+        %{
+          "type" => "Undo",
+          "object" => object
+        } = activity,
+        options
+      )
+      when is_binary(object) do
+    with %Activity{data: data} <- Activity.get_by_ap_id(object) do
+      activity
+      |> Map.put("object", data)
+      |> handle_incoming(options)
+    else
+      _e -> :error
+    end
+  end
+
   def handle_incoming(_, _), do: :error
 
   @spec get_obj_helper(String.t(), Keyword.t()) :: {:ok, Object.t()} | nil
@@ -764,7 +856,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   #  internal -> Mastodon
   #  """
 
-  def prepare_outgoing(%{"type" => "Create", "object" => object_id} = data) do
+  def prepare_outgoing(%{"type" => activity_type, "object" => object_id} = data)
+      when activity_type in ["Create", "Listen"] do
     object =
       object_id
       |> Object.normalize()
@@ -780,6 +873,27 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     {:ok, data}
   end
 
+  def prepare_outgoing(%{"type" => "Announce", "actor" => ap_id, "object" => object_id} = data) do
+    object =
+      object_id
+      |> Object.normalize()
+
+    data =
+      if Visibility.is_private?(object) && object.data["actor"] == ap_id do
+        data |> Map.put("object", object |> Map.get(:data) |> prepare_object)
+      else
+        data |> maybe_fix_object_url
+      end
+
+    data =
+      data
+      |> strip_internal_fields
+      |> Map.merge(Utils.make_json_ld_header())
+      |> Map.delete("bcc")
+
+    {:ok, data}
+  end
+
   # Mastodon Accept/Reject requires a non-normalized object containing the actor URIs,
   # because of course it does.
   def prepare_outgoing(%{"type" => "Accept"} = data) do
@@ -870,41 +984,44 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     mentions =
       object
       |> Utils.get_notified_from_object()
-      |> Enum.map(fn user ->
-        %{"type" => "Mention", "href" => user.ap_id, "name" => "@#{user.nickname}"}
-      end)
+      |> Enum.map(&build_mention_tag/1)
 
     tags = object["tag"] || []
 
     Map.put(object, "tag", tags ++ mentions)
   end
 
-  def add_emoji_tags(%User{info: %{"emoji" => _emoji} = user_info} = object) do
-    user_info = add_emoji_tags(user_info)
+  defp build_mention_tag(%{ap_id: ap_id, nickname: nickname} = _) do
+    %{"type" => "Mention", "href" => ap_id, "name" => "@#{nickname}"}
+  end
 
-    Map.put(object, :info, user_info)
+  def take_emoji_tags(%User{info: %{emoji: emoji} = _user_info} = _user) do
+    emoji
+    |> Enum.flat_map(&Map.to_list/1)
+    |> Enum.map(&build_emoji_tag/1)
   end
 
   # TODO: we should probably send mtime instead of unix epoch time for updated
   def add_emoji_tags(%{"emoji" => emoji} = object) do
     tags = object["tag"] || []
 
-    out =
-      Enum.map(emoji, fn {name, url} ->
-        %{
-          "icon" => %{"url" => url, "type" => "Image"},
-          "name" => ":" <> name <> ":",
-          "type" => "Emoji",
-          "updated" => "1970-01-01T00:00:00Z",
-          "id" => url
-        }
-      end)
+    out = Enum.map(emoji, &build_emoji_tag/1)
 
     Map.put(object, "tag", tags ++ out)
   end
 
   def add_emoji_tags(object), do: object
 
+  defp build_emoji_tag({name, url}) do
+    %{
+      "icon" => %{"url" => url, "type" => "Image"},
+      "name" => ":" <> name <> ":",
+      "type" => "Emoji",
+      "updated" => "1970-01-01T00:00:00Z",
+      "id" => url
+    }
+  end
+
   def set_conversation(object) do
     Map.put(object, "conversation", object["context"])
   end
@@ -936,17 +1053,9 @@ 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([
-      "likes",
-      "like_count",
-      "announcements",
-      "announcement_count",
-      "emoji",
-      "context_id",
-      "deleted_activity_id"
-    ])
+    |> Map.drop(Pleroma.Constants.object_internal_fields())
   end
 
   defp strip_internal_tags(%{"tag" => tags} = object) do
@@ -1006,9 +1115,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     with %User{local: false} = user <- User.get_cached_by_ap_id(ap_id),
          {:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id),
          already_ap <- User.ap_enabled?(user),
-         {:ok, user} <- user |> User.upgrade_changeset(data) |> User.update_and_set_cache() do
-      unless already_ap do
-        PleromaJobQueue.enqueue(:transmogrifier, __MODULE__, [:user_upgrade, user])
+         {:ok, user} <- upgrade_user(user, data) do
+      if not already_ap do
+        TransmogrifierWorker.enqueue("user_upgrade", %{"user_id" => user.id})
       end
 
       {:ok, user}
@@ -1018,6 +1127,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     end
   end
 
+  defp upgrade_user(user, data) do
+    user
+    |> User.upgrade_changeset(data, true)
+    |> User.update_and_set_cache()
+  end
+
   def maybe_retire_websub(ap_id) do
     # some sanity checks
     if is_binary(ap_id) && String.length(ap_id) > 8 do