Remove _misskey_reaction matching (#500)
[akkoma] / lib / pleroma / web / activity_pub / transmogrifier.ex
index cf072f7ac90102a8e7ba7ef881e6defcbc12b080..7081213e7be459ce15f8c2f89016db72db25616f 100644 (file)
@@ -19,6 +19,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   alias Pleroma.Web.ActivityPub.Pipeline
   alias Pleroma.Web.ActivityPub.Utils
   alias Pleroma.Web.ActivityPub.Visibility
+  alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
   alias Pleroma.Web.Federator
   alias Pleroma.Workers.TransmogrifierWorker
 
@@ -38,6 +39,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     |> fix_attachments()
     |> fix_context()
     |> fix_in_reply_to(options)
+    |> fix_quote_url(options)
     |> fix_emoji()
     |> fix_tag()
     |> fix_content_map()
@@ -94,29 +96,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     |> Map.put("cc", final_cc)
   end
 
-  # if as:Public is addressed, then make sure the followers collection is also addressed
-  # so that the activities will be delivered to local users.
-  def fix_implicit_addressing(%{"to" => to, "cc" => cc} = object, followers_collection) do
-    recipients = to ++ cc
-
-    if followers_collection not in recipients do
-      cond do
-        Pleroma.Constants.as_public() in cc ->
-          to = to ++ [followers_collection]
-          Map.put(object, "to", to)
-
-        Pleroma.Constants.as_public() in to ->
-          cc = cc ++ [followers_collection]
-          Map.put(object, "cc", cc)
-
-        true ->
-          object
-      end
-    else
-      object
-    end
-  end
-
   def fix_addressing(object) do
     {:ok, %User{follower_address: follower_collection}} =
       object
@@ -129,7 +108,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     |> fix_addressing_list("bto")
     |> fix_addressing_list("bcc")
     |> fix_explicit_addressing(follower_collection)
-    |> fix_implicit_addressing(follower_collection)
+    |> CommonFixes.fix_implicit_addressing(follower_collection)
   end
 
   def fix_actor(%{"attributedTo" => actor} = object) do
@@ -157,7 +136,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
         |> Map.drop(["conversation", "inReplyToAtomUri"])
       else
         e ->
-          Logger.warn("Couldn't fetch #{inspect(in_reply_to_id)}, error: #{inspect(e)}")
+          Logger.warn("Couldn't fetch reply@#{inspect(in_reply_to_id)}, error: #{inspect(e)}")
           object
       end
     else
@@ -167,6 +146,53 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
 
   def fix_in_reply_to(object, _options), do: object
 
+  def fix_quote_url(object, options \\ [])
+
+  def fix_quote_url(%{"quoteUri" => quote_url} = object, options)
+      when not is_nil(quote_url) do
+    depth = (options[:depth] || 0) + 1
+
+    if Federator.allowed_thread_distance?(depth) do
+      with {:ok, quoted_object} <- get_obj_helper(quote_url, options),
+           %Activity{} <- Activity.get_create_by_object_ap_id(quoted_object.data["id"]) do
+        object
+        |> Map.put("quoteUri", quoted_object.data["id"])
+      else
+        e ->
+          Logger.warn("Couldn't fetch quote@#{inspect(quote_url)}, error: #{inspect(e)}")
+          object
+      end
+    else
+      object
+    end
+  end
+
+  # Soapbox
+  def fix_quote_url(%{"quoteUrl" => quote_url} = object, options) do
+    object
+    |> Map.put("quoteUri", quote_url)
+    |> Map.delete("quoteUrl")
+    |> fix_quote_url(options)
+  end
+
+  # Old Fedibird (bug)
+  # https://github.com/fedibird/mastodon/issues/9
+  def fix_quote_url(%{"quoteURL" => quote_url} = object, options) do
+    object
+    |> Map.put("quoteUri", quote_url)
+    |> Map.delete("quoteURL")
+    |> fix_quote_url(options)
+  end
+
+  def fix_quote_url(%{"_misskey_quote" => quote_url} = object, options) do
+    object
+    |> Map.put("quoteUri", quote_url)
+    |> Map.delete("_misskey_quote")
+    |> fix_quote_url(options)
+  end
+
+  def fix_quote_url(object, _), do: object
+
   defp prepare_in_reply_to(in_reply_to) do
     cond do
       is_bitstring(in_reply_to) ->
@@ -320,11 +346,16 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   def fix_tag(object), do: object
 
   # content map usually only has one language so this will do for now.
-  def fix_content_map(%{"contentMap" => content_map} = object) do
+  def fix_content_map(%{"contentMap" => content_map} = object) when is_map(content_map) do
     content_groups = Map.to_list(content_map)
-    {_, content} = Enum.at(content_groups, 0)
 
-    Map.put(object, "content", content)
+    if Enum.empty?(content_groups) do
+      object
+    else
+      {_, content} = Enum.at(content_groups, 0)
+
+      Map.put(object, "content", content)
+    end
   end
 
   def fix_content_map(object), do: object
@@ -388,35 +419,26 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   def handle_incoming(
         %{
           "type" => "Like",
-          "_misskey_reaction" => reaction,
-          "tag" => _
-        } = data,
-        options
-      ) do
-    data
-    |> Map.put("type", "EmojiReact")
-    |> Map.put("content", reaction)
-    |> handle_incoming(options)
-  end
-
-  def handle_incoming(
-        %{
-          "type" => "Like",
-          "_misskey_reaction" => reaction
+          "content" => reaction
         } = data,
         options
       ) do
-    data
-    |> Map.put("type", "EmojiReact")
-    |> Map.put("content", reaction)
-    |> handle_incoming(options)
+    if Pleroma.Emoji.is_unicode_emoji?(reaction) or Pleroma.Emoji.matches_shortcode?(reaction) do
+      data
+      |> Map.put("type", "EmojiReact")
+      |> handle_incoming(options)
+    else
+      data
+      |> Map.delete("content")
+      |> handle_incoming(options)
+    end
   end
 
   def handle_incoming(
         %{"type" => "Create", "object" => %{"type" => objtype, "id" => obj_id}} = data,
         options
       )
-      when objtype in ~w{Question Answer ChatMessage Audio Video Event Article Note Page} do
+      when objtype in ~w{Question Answer Audio Video Event Article Note Page} do
     fetch_options = Keyword.put(options, :depth, (options[:depth] || 0) + 1)
 
     object =
@@ -424,8 +446,18 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
       |> strip_internal_fields()
       |> fix_type(fetch_options)
       |> fix_in_reply_to(fetch_options)
+      |> fix_quote_url(fetch_options)
+
+    # Only change the Create's context if the object's context has been modified.
+    data =
+      if data["object"]["context"] != object["context"] do
+        data
+        |> Map.put("object", object)
+        |> Map.put("context", object["context"])
+      else
+        Map.put(data, "object", object)
+      end
 
-    data = Map.put(data, "object", object)
     options = Keyword.put(options, :local, false)
 
     with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
@@ -598,6 +630,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
 
   def set_reply_to_uri(obj), do: obj
 
+  def set_quote_url(%{"quoteUri" => quote} = object) when is_binary(quote) do
+    Map.put(object, "quoteUrl", quote)
+  end
+
+  def set_quote_url(obj), do: obj
+
   @doc """
   Serialized Mastodon-compatible `replies` collection containing _self-replies_.
   Based on Mastodon's ActivityPub::NoteSerializer#replies.
@@ -652,10 +690,29 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     |> prepare_attachments
     |> set_conversation
     |> set_reply_to_uri
+    |> set_quote_url()
     |> set_replies
     |> strip_internal_fields
     |> strip_internal_tags
     |> set_type
+    |> maybe_process_history
+  end
+
+  defp maybe_process_history(%{"formerRepresentations" => %{"orderedItems" => history}} = object) do
+    processed_history =
+      Enum.map(
+        history,
+        fn
+          item when is_map(item) -> prepare_object(item)
+          item -> item
+        end
+      )
+
+    put_in(object, ["formerRepresentations", "orderedItems"], processed_history)
+  end
+
+  defp maybe_process_history(object) do
+    object
   end
 
   #  @doc
@@ -680,6 +737,21 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     {:ok, data}
   end
 
+  def prepare_outgoing(%{"type" => "Update", "object" => %{"type" => objtype} = object} = data)
+      when objtype in Pleroma.Constants.updatable_object_types() do
+    object =
+      object
+      |> prepare_object
+
+    data =
+      data
+      |> Map.put("object", object)
+      |> Map.merge(Utils.make_json_ld_header())
+      |> Map.delete("bcc")
+
+    {:ok, data}
+  end
+
   def prepare_outgoing(%{"type" => "Announce", "actor" => ap_id, "object" => object_id} = data) do
     object =
       object_id
@@ -757,7 +829,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
       Map.put(data, "object", external_url)
     else
       {:fetch, e} ->
-        Logger.error("Couldn't fetch #{object} #{inspect(e)}")
+        Logger.error("Couldn't fetch fixed_object@#{object} #{inspect(e)}")
         data
 
       _ ->
@@ -846,9 +918,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     Map.put(object, "attributedTo", attributed_to)
   end
 
-  # TODO: Revisit this
-  def prepare_attachments(%{"type" => "ChatMessage"} = object), do: object
-
   def prepare_attachments(object) do
     attachments =
       object