activitypub: transmogrifier: add necessary translations for kroeg
[akkoma] / lib / pleroma / web / activity_pub / transmogrifier.ex
index 7b0d124b26ce3bede04bfed8ccf84d8c9f34a3d4..e5fb6e033d708ee71b871233cc765cbda52e9003 100644 (file)
@@ -13,31 +13,77 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
 
   require Logger
 
+  def get_actor(%{"actor" => actor}) when is_binary(actor) do
+    actor
+  end
+
+  def get_actor(%{"actor" => actor}) when is_list(actor) do
+    Enum.at(actor, 0)
+  end
+
+  def get_actor(%{"actor" => actor}) when is_map(actor) do
+    actor["id"]
+  end
+
+  def get_actor(%{"actor" => actor_list}) do
+    Enum.find(actor_list, fn %{"type" => type} -> type == "Person" end)
+    |> Map.get("id")
+  end
+
   @doc """
   Modifies an incoming AP object (mastodon format) to our internal format.
   """
   def fix_object(object) do
     object
-    |> Map.put("actor", object["attributedTo"])
+    |> fix_actor
     |> fix_attachments
     |> fix_context
     |> fix_in_reply_to
     |> fix_emoji
     |> fix_tag
+    |> fix_content_map
+    |> fix_addressing
+  end
+
+  def fix_addressing_list(map, field) do
+    if is_binary(map[field]) do
+      map
+      |> Map.put(field, [map[field]])
+    else
+      map
+    end
+  end
+
+  def fix_addressing(map) do
+    map
+    |> fix_addressing_list("to")
+    |> fix_addressing_list("cc")
+    |> fix_addressing_list("bto")
+    |> fix_addressing_list("bcc")
+  end
+
+  def fix_actor(%{"attributedTo" => actor} = object) do
+    object
+    |> Map.put("actor", get_actor(%{"actor" => actor}))
   end
 
   def fix_in_reply_to(%{"inReplyTo" => in_reply_to_id} = object)
       when not is_nil(in_reply_to_id) do
     case ActivityPub.fetch_object_from_id(in_reply_to_id) do
       {:ok, replied_object} ->
-        activity = Activity.get_create_activity_by_object_ap_id(replied_object.data["id"])
-
-        object
-        |> Map.put("inReplyTo", replied_object.data["id"])
-        |> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id)
-        |> Map.put("inReplyToStatusId", activity.id)
-        |> Map.put("conversation", replied_object.data["context"] || object["conversation"])
-        |> Map.put("context", replied_object.data["context"] || object["conversation"])
+        with %Activity{} = activity <-
+               Activity.get_create_activity_by_object_ap_id(replied_object.data["id"]) do
+          object
+          |> Map.put("inReplyTo", replied_object.data["id"])
+          |> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id)
+          |> Map.put("inReplyToStatusId", activity.id)
+          |> Map.put("conversation", replied_object.data["context"] || object["conversation"])
+          |> Map.put("context", replied_object.data["context"] || object["conversation"])
+        else
+          e ->
+            Logger.error("Couldn't fetch #{object["inReplyTo"]} #{inspect(e)}")
+            object
+        end
 
       e ->
         Logger.error("Couldn't fetch #{object["inReplyTo"]} #{inspect(e)}")
@@ -102,10 +148,28 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     |> Map.put("tag", combined)
   end
 
+  # content map usually only has one language so this will do for now.
+  def fix_content_map(%{"contentMap" => content_map} = object) do
+    content_groups = Map.to_list(content_map)
+    {_, content} = Enum.at(content_groups, 0)
+
+    object
+    |> Map.put("content", content)
+  end
+
+  def fix_content_map(object), do: object
+
   # TODO: validate those with a Ecto scheme
   # - tags
   # - emoji
-  def handle_incoming(%{"type" => "Create", "object" => %{"type" => "Note"} = object} = data) do
+  def handle_incoming(%{"type" => "Create", "object" => %{"type" => objtype} = object} = data)
+      when objtype in ["Article", "Note"] do
+    actor = get_actor(data)
+
+    data =
+      Map.put(data, "actor", actor)
+      |> fix_addressing
+
     with nil <- Activity.get_create_activity_by_object_ap_id(object["id"]),
          %User{} = user <- User.get_or_fetch_by_ap_id(data["actor"]) do
       object = fix_object(data["object"])
@@ -138,7 +202,13 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
          %User{} = follower <- User.get_or_fetch_by_ap_id(follower),
          {:ok, activity} <- ActivityPub.follow(follower, followed, id, false) do
       if not User.locked?(followed) do
-        ActivityPub.accept(%{to: [follower.ap_id], actor: followed.ap_id, object: data, local: true})
+        ActivityPub.accept(%{
+          to: [follower.ap_id],
+          actor: followed.ap_id,
+          object: data,
+          local: true
+        })
+
         User.follow(follower, followed)
       end
 
@@ -254,7 +324,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
       {:ok, new_user_data} = ActivityPub.user_data_from_user_object(object)
 
       banner = new_user_data[:info]["banner"]
-      locked = new_user_data[:info]["locked"]
+      locked = new_user_data[:info]["locked"] || false
 
       update_data =
         new_user_data
@@ -306,7 +376,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     with %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
          {:ok, object} <-
            get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id),
-         {:ok, activity, _, _} <- ActivityPub.unannounce(actor, object, id, false) do
+         {:ok, activity, _} <- ActivityPub.unannounce(actor, object, id, false) do
       {:ok, activity}
     else
       _e -> :error
@@ -389,7 +459,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   def handle_incoming(_), do: :error
 
   def get_obj_helper(id) do
-    if object = Object.get_by_ap_id(id), do: {:ok, object}, else: nil
+    if object = Object.normalize(id), do: {:ok, object}, else: nil
   end
 
   def set_reply_to_uri(%{"inReplyTo" => inReplyTo} = object) do
@@ -434,6 +504,44 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     {: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
+    with follow_activity <- Activity.normalize(data["object"]) do
+      object = %{
+        "actor" => follow_activity.actor,
+        "object" => follow_activity.data["object"],
+        "id" => follow_activity.data["id"],
+        "type" => "Follow"
+      }
+
+      data =
+        data
+        |> Map.put("object", object)
+        |> Map.put("@context", "https://www.w3.org/ns/activitystreams")
+
+      {:ok, data}
+    end
+  end
+
+  def prepare_outgoing(%{"type" => "Reject"} = data) do
+    with follow_activity <- Activity.normalize(data["object"]) do
+      object = %{
+        "actor" => follow_activity.actor,
+        "object" => follow_activity.data["object"],
+        "id" => follow_activity.data["id"],
+        "type" => "Follow"
+      }
+
+      data =
+        data
+        |> Map.put("object", object)
+        |> Map.put("@context", "https://www.w3.org/ns/activitystreams")
+
+      {:ok, data}
+    end
+  end
+
   def prepare_outgoing(%{"type" => _type} = data) do
     data =
       data