Add addressable lists
authorEgor Kislitsyn <egor@kislitsyn.com>
Wed, 1 May 2019 09:11:17 +0000 (16:11 +0700)
committerEgor Kislitsyn <egor@kislitsyn.com>
Wed, 1 May 2019 09:11:17 +0000 (16:11 +0700)
lib/pleroma/web/activity_pub/activity_pub.ex
lib/pleroma/web/activity_pub/transmogrifier.ex
lib/pleroma/web/common_api/common_api.ex
lib/pleroma/web/common_api/utils.ex
lib/pleroma/web/salmon/salmon.ex

index 604ffae7b4e50df0d525e94c0f7dbda0711d217c..84d7f47b1cc275a5f45b1892ea60818e63af7824 100644 (file)
@@ -28,19 +28,16 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
   # For Announce activities, we filter the recipients based on following status for any actors
   # that match actual users.  See issue #164 for more information about why this is necessary.
   defp get_recipients(%{"type" => "Announce"} = data) do
-    to = data["to"] || []
-    cc = data["cc"] || []
+    to = Map.get(data, "to", [])
+    cc = Map.get(data, "cc", [])
+    bcc = Map.get(data, "bcc", [])
     actor = User.get_cached_by_ap_id(data["actor"])
 
     recipients =
-      (to ++ cc)
-      |> Enum.filter(fn recipient ->
+      Enum.filter(Enum.concat([to, cc, bcc]), fn recipient ->
         case User.get_cached_by_ap_id(recipient) do
-          nil ->
-            true
-
-          user ->
-            User.following?(user, actor)
+          nil -> true
+          user -> User.following?(user, actor)
         end
       end)
 
@@ -48,17 +45,19 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
   end
 
   defp get_recipients(%{"type" => "Create"} = data) do
-    to = data["to"] || []
-    cc = data["cc"] || []
-    actor = data["actor"] || []
-    recipients = (to ++ cc ++ [actor]) |> Enum.uniq()
+    to = Map.get(data, "to", [])
+    cc = Map.get(data, "cc", [])
+    bcc = Map.get(data, "bcc", [])
+    actor = Map.get(data, "actor", [])
+    recipients = [to, cc, bcc, [actor]] |> Enum.concat() |> Enum.uniq()
     {recipients, to, cc}
   end
 
   defp get_recipients(data) do
-    to = data["to"] || []
-    cc = data["cc"] || []
-    recipients = to ++ cc
+    to = Map.get(data, "to", [])
+    cc = Map.get(data, "cc", [])
+    bcc = Map.get(data, "bcc", [])
+    recipients = Enum.concat([to, cc, bcc])
     {recipients, to, cc}
   end
 
@@ -917,22 +916,55 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     end
   end
 
-  def publish(actor, activity) do
-    remote_followers =
+  defp recipients(actor, activity) do
+    Pleroma.Web.Salmon.remote_users(activity) ++
       if actor.follower_address in activity.recipients do
         {:ok, followers} = User.get_followers(actor)
         followers |> Enum.filter(&(!&1.local))
       else
         []
       end
+  end
 
+  def publish(actor, %{data: %{"bcc" => bcc}} = activity) when is_list(bcc) and bcc != [] do
     public = is_public?(activity)
+    {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
+
+    recipients = recipients(actor, activity)
+
+    recipients
+    |> Enum.filter(&User.ap_enabled?/1)
+    |> Enum.map(fn %{info: %{source_data: data}} -> data["inbox"] end)
+    |> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
+    |> Instances.filter_reachable()
+    |> Enum.each(fn {inbox, unreachable_since} ->
+      %User{ap_id: cc} =
+        Enum.find(recipients, fn %{info: %{source_data: data}} -> data["inbox"] == inbox end)
 
+      json =
+        data
+        |> Map.put("cc", [cc])
+        |> Map.put("directMessage", true)
+        |> Jason.encode!()
+
+      Federator.publish_single_ap(%{
+        inbox: inbox,
+        json: json,
+        actor: actor,
+        id: activity.data["id"],
+        unreachable_since: unreachable_since
+      })
+    end)
+  end
+
+  def publish(actor, activity) do
+    public = is_public?(activity)
     {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
+
     json = Jason.encode!(data)
 
-    (Pleroma.Web.Salmon.remote_users(activity) ++ remote_followers)
-    |> Enum.filter(fn user -> User.ap_enabled?(user) end)
+    recipients(actor, activity)
+    |> Enum.filter(&User.ap_enabled?/1)
     |> Enum.map(fn %{info: %{source_data: data}} ->
       (is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"]
     end)
index b1e859d7cf83d7bd2a5fd0ac28fdd94d8893e122..3b7193eaa8e831dbe37ec94f5f067616f8e4ef34 100644 (file)
@@ -741,13 +741,16 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
 
   def prepare_outgoing(%{"type" => "Create", "object" => object_id} = data) do
     object =
-      Object.normalize(object_id).data
+      object_id
+      |> Object.normalize()
+      |> Map.get(:data)
       |> prepare_object
 
     data =
       data
       |> Map.put("object", object)
       |> Map.merge(Utils.make_json_ld_header())
+      |> Map.delete("bcc")
 
     {:ok, data}
   end
index cfbc5dc1074fd643f6f24508db98c6d447e532b4..4ca59110f0e88570f5f72cb5e5c0064154f48ce6 100644 (file)
@@ -119,6 +119,10 @@ defmodule Pleroma.Web.CommonAPI do
       when visibility in ~w{public unlisted private direct},
       do: visibility
 
+  def get_visibility(%{"visibility" => "list:" <> list_id}) do
+    {:list, String.to_integer(list_id)}
+  end
+
   def get_visibility(%{"in_reply_to_status_id" => status_id}) when not is_nil(status_id) do
     case get_replied_to_activity(status_id) do
       nil ->
@@ -149,6 +153,7 @@ defmodule Pleroma.Web.CommonAPI do
              visibility
            ),
          {to, cc} <- to_for_user_and_mentions(user, mentions, in_reply_to, visibility),
+         {:ok, bcc} <- bcc_for_list(user, visibility),
          context <- make_context(in_reply_to),
          cw <- data["spoiler_text"],
          full_payload <- String.trim(status <> (data["spoiler_text"] || "")),
@@ -174,19 +179,16 @@ defmodule Pleroma.Web.CommonAPI do
                Map.put(acc, name, "#{Pleroma.Web.Endpoint.static_url()}#{file}")
              end)
            ) do
-      res =
-        ActivityPub.create(
-          %{
-            to: to,
-            actor: user,
-            context: context,
-            object: object,
-            additional: %{"cc" => cc, "directMessage" => visibility == "direct"}
-          },
-          Pleroma.Web.ControllerHelper.truthy_param?(data["preview"]) || false
-        )
-
-      res
+      ActivityPub.create(
+        %{
+          to: to,
+          actor: user,
+          context: context,
+          object: object,
+          additional: %{"cc" => cc, "bcc" => bcc, "directMessage" => visibility == "direct"}
+        },
+        Pleroma.Web.ControllerHelper.truthy_param?(data["preview"]) || false
+      )
     end
   end
 
index 887f878c440ed2a6e6550017596c3f3764d1db12..83a745b58bec926dca90e64cfa4eae1a3f735a61 100644 (file)
@@ -8,6 +8,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
   alias Pleroma.Activity
   alias Pleroma.Config
   alias Pleroma.Formatter
+  alias Pleroma.List
   alias Pleroma.Object
   alias Pleroma.Repo
   alias Pleroma.User
@@ -102,6 +103,20 @@ defmodule Pleroma.Web.CommonAPI.Utils do
     end
   end
 
+  def to_for_user_and_mentions(_user, _mentions, _inReplyTo, _), do: {[], []}
+
+  def bcc_for_list(user, {:list, list_id}) do
+    with {_, %List{} = list} <- {:list, List.get(list_id, user)},
+         {:ok, following} <- List.get_following(list) do
+      {:ok, Enum.map(following, & &1.ap_id)}
+    else
+      {:list, _} -> {:error, "List not found"}
+      err -> err
+    end
+  end
+
+  def bcc_for_list(_, _), do: {:ok, []}
+
   def make_content_html(
         status,
         attachments,
index 0a9e51656db5e9fb56bb768c81694976a257ccb9..4d89f4bdb17e9ffaf9ee448388f7431376d0956c 100644 (file)
@@ -157,10 +157,12 @@ defmodule Pleroma.Web.Salmon do
   end
 
   def remote_users(%{data: %{"to" => to} = data}) do
-    to = to ++ (data["cc"] || [])
+    cc = Map.get(data, "cc", [])
+    bcc = Map.get(data, "bcc", [])
 
-    to
-    |> Enum.map(fn id -> User.get_cached_by_ap_id(id) end)
+    [to, cc, bcc]
+    |> Enum.concat()
+    |> Enum.map(&User.get_cached_by_ap_id/1)
     |> Enum.filter(fn user -> user && !user.local end)
   end