Use pseudo ap id of a list in BCC
authorEgor Kislitsyn <egor@kislitsyn.com>
Mon, 13 May 2019 09:15:14 +0000 (16:15 +0700)
committerEgor Kislitsyn <egor@kislitsyn.com>
Mon, 13 May 2019 09:15:14 +0000 (16:15 +0700)
lib/pleroma/list.ex
lib/pleroma/web/activity_pub/activity_pub.ex
lib/pleroma/web/common_api/common_api.ex
lib/pleroma/web/common_api/utils.ex
lib/pleroma/web/salmon/salmon.ex

index a5b1cad680ddf20dc3eb23e5981917cfa765a840..81b842e9cd909048c77b7dbaf37e880e9404b1f7 100644 (file)
@@ -12,6 +12,8 @@ defmodule Pleroma.List do
   alias Pleroma.Repo
   alias Pleroma.User
 
+  @ap_id_regex ~r/^\/users\/(?<nickname>\w+)\/lists\/(?<list_id>\d+)/
+
   schema "lists" do
     belongs_to(:user, User, type: Pleroma.FlakeId)
     field(:title, :string)
@@ -32,6 +34,12 @@ defmodule Pleroma.List do
     |> validate_required([:following])
   end
 
+  def ap_id(%User{nickname: nickname}, list_id) do
+    Pleroma.Web.Endpoint.url() <> "/users/#{nickname}/lists/#{list_id}"
+  end
+
+  def ap_id({nickname, list_id}), do: ap_id(%User{nickname: nickname}, list_id)
+
   def for_user(user, _opts) do
     query =
       from(
@@ -55,6 +63,19 @@ defmodule Pleroma.List do
     Repo.one(query)
   end
 
+  def get_by_ap_id(ap_id) do
+    host = Pleroma.Web.Endpoint.host()
+
+    with %{host: ^host, path: path} <- URI.parse(ap_id),
+         %{"list_id" => list_id, "nickname" => nickname} <-
+           Regex.named_captures(@ap_id_regex, path),
+         %User{} = user <- User.get_cached_by_nickname(nickname) do
+      get(list_id, user)
+    else
+      _ -> nil
+    end
+  end
+
   def get_following(%Pleroma.List{following: following} = _list) do
     q =
       from(
@@ -125,4 +146,15 @@ defmodule Pleroma.List do
     |> follow_changeset(attrs)
     |> Repo.update()
   end
+
+  def memberships(%User{follower_address: follower_address}) do
+    Pleroma.List
+    |> where([l], ^follower_address in l.following)
+    |> join(:inner, [l], u in User, on: l.user_id == u.id)
+    |> select([l, u], {u.nickname, l.id})
+    |> Repo.all()
+    |> Enum.map(&ap_id/1)
+  end
+
+  def memberships(_), do: []
 end
index 84d7f47b1cc275a5f45b1892ea60818e63af7824..3b71e03698d241ed55c2fbbfac63fbabb20982d3 100644 (file)
@@ -783,9 +783,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
   end
 
   def fetch_activities_query(recipients, opts \\ %{}) do
-    base_query = from(activity in Activity)
-
-    base_query
+    Activity
     |> maybe_preload_objects(opts)
     |> restrict_recipients(recipients, opts["user"])
     |> restrict_tag(opts)
@@ -807,9 +805,29 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
   end
 
   def fetch_activities(recipients, opts \\ %{}) do
-    fetch_activities_query(recipients, opts)
+    list_memberships = Pleroma.List.memberships(opts["user"])
+
+    fetch_activities_query(recipients ++ list_memberships, opts)
     |> Pagination.fetch_paginated(opts)
     |> Enum.reverse()
+    |> maybe_update_cc(list_memberships, opts["user"])
+  end
+
+  defp maybe_update_cc(activities, [], _), do: activities
+  defp maybe_update_cc(activities, _, nil), do: activities
+
+  defp maybe_update_cc(activities, list_memberships, user) do
+    Enum.map(activities, fn
+      %{data: %{"bcc" => bcc}} = activity when is_list(bcc) ->
+        if Enum.any?(bcc, &(&1 in list_memberships)) do
+          update_in(activity.data["cc"], &[user.ap_id | &1])
+        else
+          activity
+        end
+
+      activity ->
+        activity
+    end)
   end
 
   def fetch_activities_bounded(recipients_to, recipients_cc, opts \\ %{}) do
@@ -917,13 +935,23 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
   end
 
   defp recipients(actor, activity) do
-    Pleroma.Web.Salmon.remote_users(activity) ++
+    followers =
       if actor.follower_address in activity.recipients do
         {:ok, followers} = User.get_followers(actor)
-        followers |> Enum.filter(&(!&1.local))
+        Enum.filter(followers, &(!&1.local))
       else
         []
       end
+
+    Pleroma.Web.Salmon.remote_users(actor, activity) ++ followers
+  end
+
+  defp get_cc_ap_ids(ap_id, recipients) do
+    host = Map.get(URI.parse(ap_id), :host)
+
+    recipients
+    |> Enum.filter(fn %User{ap_id: ap_id} -> Map.get(URI.parse(ap_id), :host) == host end)
+    |> Enum.map(& &1.ap_id)
   end
 
   def publish(actor, %{data: %{"bcc" => bcc}} = activity) when is_list(bcc) and bcc != [] do
@@ -938,12 +966,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     |> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
     |> Instances.filter_reachable()
     |> Enum.each(fn {inbox, unreachable_since} ->
-      %User{ap_id: cc} =
+      %User{ap_id: ap_id} =
         Enum.find(recipients, fn %{info: %{source_data: data}} -> data["inbox"] == inbox end)
 
+      cc = get_cc_ap_ids(ap_id, recipients)
+
       json =
         data
-        |> Map.put("cc", [cc])
+        |> Map.put("cc", cc)
         |> Map.put("directMessage", true)
         |> Jason.encode!()
 
index 4ca59110f0e88570f5f72cb5e5c0064154f48ce6..d47d5788ced936adc09b09d822c6fde726d02318 100644 (file)
@@ -153,7 +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),
+         bcc <- bcc_for_list(user, visibility),
          context <- make_context(in_reply_to),
          cw <- data["spoiler_text"],
          full_payload <- String.trim(status <> (data["spoiler_text"] || "")),
@@ -197,7 +197,7 @@ defmodule Pleroma.Web.CommonAPI do
     user =
       with emoji <- emoji_from_profile(user),
            source_data <- (user.info.source_data || %{}) |> Map.put("tag", emoji),
-           info_cng <- Pleroma.User.Info.set_source_data(user.info, source_data),
+           info_cng <- User.Info.set_source_data(user.info, source_data),
            change <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
            {:ok, user} <- User.update_and_set_cache(change) do
         user
@@ -230,7 +230,7 @@ defmodule Pleroma.Web.CommonAPI do
          } = activity <- get_by_id_or_ap_id(id_or_ap_id),
          true <- Enum.member?(object_to, "https://www.w3.org/ns/activitystreams#Public"),
          %{valid?: true} = info_changeset <-
-           Pleroma.User.Info.add_pinnned_activity(user.info, activity),
+           User.Info.add_pinnned_activity(user.info, activity),
          changeset <-
            Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset),
          {:ok, _user} <- User.update_and_set_cache(changeset) do
@@ -247,7 +247,7 @@ defmodule Pleroma.Web.CommonAPI do
   def unpin(id_or_ap_id, user) do
     with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
          %{valid?: true} = info_changeset <-
-           Pleroma.User.Info.remove_pinnned_activity(user.info, activity),
+           User.Info.remove_pinnned_activity(user.info, activity),
          changeset <-
            Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset),
          {:ok, _user} <- User.update_and_set_cache(changeset) do
index 83a745b58bec926dca90e64cfa4eae1a3f735a61..32c3b4b980f7272b19167890401791fb395a4588 100644 (file)
@@ -8,7 +8,6 @@ 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
@@ -106,16 +105,10 @@ defmodule Pleroma.Web.CommonAPI.Utils do
   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
+    [Pleroma.List.ap_id(user, list_id)]
   end
 
-  def bcc_for_list(_, _), do: {:ok, []}
+  def bcc_for_list(_, _), do: []
 
   def make_content_html(
         status,
index 4d89f4bdb17e9ffaf9ee448388f7431376d0956c..ca51255f34d926a216f330875587ee0dfde60bfa 100644 (file)
@@ -156,9 +156,22 @@ defmodule Pleroma.Web.Salmon do
     {:ok, salmon}
   end
 
-  def remote_users(%{data: %{"to" => to} = data}) do
+  def remote_users(%User{id: user_id}, %{data: %{"to" => to} = data}) do
     cc = Map.get(data, "cc", [])
-    bcc = Map.get(data, "bcc", [])
+
+    bcc =
+      data
+      |> Map.get("bcc", [])
+      |> Enum.reduce([], fn ap_id, bcc ->
+        case Pleroma.List.get_by_ap_id(ap_id) do
+          %Pleroma.List{user_id: ^user_id} = list ->
+            {:ok, following} = Pleroma.List.get_following(list)
+            bcc ++ Enum.map(following, & &1.ap_id)
+
+          _ ->
+            bcc
+        end
+      end)
 
     [to, cc, bcc]
     |> Enum.concat()
@@ -220,7 +233,7 @@ defmodule Pleroma.Web.Salmon do
       {:ok, private, _} = keys_from_pem(keys)
       {:ok, feed} = encode(private, feed)
 
-      remote_users = remote_users(activity)
+      remote_users = remote_users(user, activity)
 
       salmon_urls = Enum.map(remote_users, & &1.info.salmon)
       reachable_urls_metadata = Instances.filter_reachable(salmon_urls)