From 1f0be71ea433971de874a71ba1dafd101f4301b6 Mon Sep 17 00:00:00 2001
From: KokaKiwi <kokakiwi@kokakiwi.net>
Date: Sun, 24 Feb 2019 18:45:29 +0100
Subject: [PATCH 1/7] Make activity announceable by its author.

---
 lib/pleroma/web/activity_pub/activity_pub.ex | 2 +-
 lib/pleroma/web/activity_pub/visibility.ex   | 4 ++++
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 95f994c17..c58b48443 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -346,7 +346,7 @@ def announce(
         local \\ true,
         public \\ true
       ) do
-    with true <- is_public?(object),
+    with true <- is_announceable?(object, user),
          announce_data <- make_announce_data(user, object, activity_id, public),
          {:ok, activity} <- insert(announce_data, local),
          {:ok, object} <- add_announce_to_object(activity, object),
diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex
index dfb166b65..021efd30f 100644
--- a/lib/pleroma/web/activity_pub/visibility.ex
+++ b/lib/pleroma/web/activity_pub/visibility.ex
@@ -27,6 +27,10 @@ def is_private?(activity) do
     end
   end
 
+  def is_announceable?(activity, user) do
+    is_public?(activity) || activity.data["actor"] == user.ap_id
+  end
+
   def is_direct?(%Activity{data: %{"directMessage" => true}}), do: true
   def is_direct?(%Object{data: %{"directMessage" => true}}), do: true
 

From fe538973ddbdb7b3216e8da1defaa57adb63e890 Mon Sep 17 00:00:00 2001
From: Thibaut Girka <thib@sitedethib.com>
Date: Tue, 1 Oct 2019 17:49:52 +0200
Subject: [PATCH 2/7] Ensure self-announces do not widen the audience of the
 original post

---
 lib/pleroma/web/activity_pub/activity_pub.ex | 2 +-
 lib/pleroma/web/activity_pub/visibility.ex   | 5 +++--
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index c58b48443..c52efb578 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -346,7 +346,7 @@ def announce(
         local \\ true,
         public \\ true
       ) do
-    with true <- is_announceable?(object, user),
+    with true <- is_announceable?(object, user, public),
          announce_data <- make_announce_data(user, object, activity_id, public),
          {:ok, activity} <- insert(announce_data, local),
          {:ok, object} <- add_announce_to_object(activity, object),
diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex
index 021efd30f..270d0fa02 100644
--- a/lib/pleroma/web/activity_pub/visibility.ex
+++ b/lib/pleroma/web/activity_pub/visibility.ex
@@ -27,8 +27,9 @@ def is_private?(activity) do
     end
   end
 
-  def is_announceable?(activity, user) do
-    is_public?(activity) || activity.data["actor"] == user.ap_id
+  def is_announceable?(activity, user, public \\ true) do
+    is_public?(activity) ||
+      (!public && is_private?(activity) && activity.data["actor"] == user.ap_id)
   end
 
   def is_direct?(%Activity{data: %{"directMessage" => true}}), do: true

From e0b654e202554f001a5de3df4ccb8021fd3a517a Mon Sep 17 00:00:00 2001
From: Thibaut Girka <thib@sitedethib.com>
Date: Tue, 1 Oct 2019 17:51:27 +0200
Subject: [PATCH 3/7] Add tests

---
 test/web/activity_pub/activity_pub_test.exs | 33 +++++++++++++++++++++
 1 file changed, 33 insertions(+)

diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs
index a203d1d30..f29497847 100644
--- a/test/web/activity_pub/activity_pub_test.exs
+++ b/test/web/activity_pub/activity_pub_test.exs
@@ -839,6 +839,39 @@ test "adds an announce activity to the db" do
     end
   end
 
+  describe "announcing a private object" do
+    test "adds an announce activity to the db if the audience is not widened" do
+      user = insert(:user)
+      {:ok, note_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
+      object = Object.normalize(note_activity)
+
+      {:ok, announce_activity, object} = ActivityPub.announce(user, object, nil, true, false)
+
+      assert announce_activity.data["to"] == [User.ap_followers(user)]
+
+      assert announce_activity.data["object"] == object.data["id"]
+      assert announce_activity.data["actor"] == user.ap_id
+      assert announce_activity.data["context"] == object.data["context"]
+    end
+
+    test "does not add an announce activity to the db if the audience is widened" do
+      user = insert(:user)
+      {:ok, note_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
+      object = Object.normalize(note_activity)
+
+      assert {:error, _} = ActivityPub.announce(user, object, nil, true, true)
+    end
+
+    test "does not add an announce activity to the db if the announcer is not the author" do
+      user = insert(:user)
+      announcer = insert(:user)
+      {:ok, note_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
+      object = Object.normalize(note_activity)
+
+      assert {:error, _} = ActivityPub.announce(announcer, object, nil, true, false)
+    end
+  end
+
   describe "unannouncing an object" do
     test "unannouncing a previously announced object" do
       note_activity = insert(:note_activity)

From 4c1f158f5de95581f1489be32614e0e75bc77ba4 Mon Sep 17 00:00:00 2001
From: Thibaut Girka <thib@sitedethib.com>
Date: Tue, 1 Oct 2019 18:38:23 +0200
Subject: [PATCH 4/7] Allow users to announce privately, including own private
 notes

---
 lib/pleroma/web/common_api/common_api.ex          | 15 ++++++++++++---
 .../mastodon_api/controllers/status_controller.ex |  4 ++--
 2 files changed, 14 insertions(+), 5 deletions(-)

diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex
index 2ec017ff8..677a53ddf 100644
--- a/lib/pleroma/web/common_api/common_api.ex
+++ b/lib/pleroma/web/common_api/common_api.ex
@@ -76,11 +76,12 @@ def delete(activity_id, user) do
     end
   end
 
-  def repeat(id_or_ap_id, user) do
+  def repeat(id_or_ap_id, user, params \\ %{}) do
     with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
          object <- Object.normalize(activity),
-         nil <- Utils.get_existing_announce(user.ap_id, object) do
-      ActivityPub.announce(user, object)
+         nil <- Utils.get_existing_announce(user.ap_id, object),
+         public <- get_announce_visibility(object, params) do
+      ActivityPub.announce(user, object, nil, true, public)
     else
       _ -> {:error, dgettext("errors", "Could not repeat")}
     end
@@ -169,6 +170,14 @@ defp normalize_and_validate_choices(choices, object) do
     end
   end
 
+  def get_announce_visibility(_, %{"visibility" => visibility})
+      when visibility in ~w{public unlisted private direct},
+      do: visibility in ~w(public unlisted)
+
+  def get_announce_visibility(object, _) do
+    Visibility.is_public?(object)
+  end
+
   def get_visibility(_, _, %Participation{}), do: {"direct", "direct"}
 
   def get_visibility(%{"visibility" => visibility}, in_reply_to, _)
diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
index fb6fd7676..51456d453 100644
--- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
@@ -125,8 +125,8 @@ def delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
   end
 
   @doc "POST /api/v1/statuses/:id/reblog"
-  def reblog(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
-    with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user),
+  def reblog(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id} = params) do
+    with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user, params),
          %Activity{} = announce <- Activity.normalize(announce.data) do
       try_render(conn, "show.json", %{activity: announce, for: user, as: :activity})
     end

From 7d5a9f3f6d393c744364148568cfb9b0249789fc Mon Sep 17 00:00:00 2001
From: Thibaut Girka <thib@sitedethib.com>
Date: Tue, 1 Oct 2019 19:08:25 +0200
Subject: [PATCH 5/7] Add tests for privately announcing statuses via API

---
 test/web/common_api/common_api_test.exs        | 12 ++++++++++++
 .../controllers/status_controller_test.exs     | 18 ++++++++++++++++++
 2 files changed, 30 insertions(+)

diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs
index 0f4a5eb25..2d3c41e82 100644
--- a/test/web/common_api/common_api_test.exs
+++ b/test/web/common_api/common_api_test.exs
@@ -231,6 +231,18 @@ test "repeating a status" do
       {:ok, %Activity{}, _} = CommonAPI.repeat(activity.id, user)
     end
 
+    test "repeating a status privately" do
+      user = insert(:user)
+      other_user = insert(:user)
+
+      {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
+
+      {:ok, %Activity{} = announce_activity, _} =
+        CommonAPI.repeat(activity.id, user, %{"visibility" => "private"})
+
+      assert Visibility.is_private?(announce_activity)
+    end
+
     test "favoriting a status" do
       user = insert(:user)
       other_user = insert(:user)
diff --git a/test/web/mastodon_api/controllers/status_controller_test.exs b/test/web/mastodon_api/controllers/status_controller_test.exs
index b194feae6..727a233e7 100644
--- a/test/web/mastodon_api/controllers/status_controller_test.exs
+++ b/test/web/mastodon_api/controllers/status_controller_test.exs
@@ -547,6 +547,24 @@ test "reblogs and returns the reblogged status", %{conn: conn} do
       assert to_string(activity.id) == id
     end
 
+    test "reblogs privately and returns the reblogged status", %{conn: conn} do
+      activity = insert(:note_activity)
+      user = insert(:user)
+
+      conn =
+        conn
+        |> assign(:user, user)
+        |> post("/api/v1/statuses/#{activity.id}/reblog", %{"visibility" => "private"})
+
+      assert %{
+               "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 0},
+               "reblogged" => true,
+               "visibility" => "private"
+             } = json_response(conn, 200)
+
+      assert to_string(activity.id) == id
+    end
+
     test "reblogged status for another user", %{conn: conn} do
       activity = insert(:note_activity)
       user1 = insert(:user)

From 43e3db0951c34859932f20d8c82284343a82fcf1 Mon Sep 17 00:00:00 2001
From: Thibaut Girka <thib@sitedethib.com>
Date: Tue, 1 Oct 2019 19:28:51 +0200
Subject: [PATCH 6/7] Fix returned visibility of announces in MastodonAPI

---
 lib/pleroma/web/mastodon_api/views/status_view.ex | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index 3262427ec..9b8dd3086 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -125,7 +125,7 @@ def render(
       pinned: pinned?(activity, user),
       sensitive: false,
       spoiler_text: "",
-      visibility: "public",
+      visibility: get_visibility(activity),
       media_attachments: reblogged[:media_attachments] || [],
       mentions: mentions,
       tags: reblogged[:tags] || [],

From 427d0c2a007db6c8424c64a8f3504420e5203bef Mon Sep 17 00:00:00 2001
From: Thibaut Girka <thib@sitedethib.com>
Date: Tue, 1 Oct 2019 21:40:35 +0200
Subject: [PATCH 7/7] Store private announcements in
 object.data["announcements"], filter them on display

---
 lib/pleroma/web/activity_pub/utils.ex         |  2 +-
 .../controllers/status_controller.ex          | 14 +++++++++++++-
 .../controllers/status_controller_test.exs    | 19 ++++++++++++++++++-
 3 files changed, 32 insertions(+), 3 deletions(-)

diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
index 2ba182f4e..0828591ee 100644
--- a/lib/pleroma/web/activity_pub/utils.ex
+++ b/lib/pleroma/web/activity_pub/utils.ex
@@ -494,7 +494,7 @@ def make_unlike_data(
   @spec add_announce_to_object(Activity.t(), Object.t()) ::
           {:ok, Object.t()} | {:error, Ecto.Changeset.t()}
   def add_announce_to_object(
-        %Activity{data: %{"actor" => actor, "cc" => [Pleroma.Constants.as_public()]}},
+        %Activity{data: %{"actor" => actor}},
         object
       ) do
     announcements = take_announcements(object)
diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
index 51456d453..79cced163 100644
--- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
@@ -242,7 +242,19 @@ def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
   def reblogged_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
     with %Activity{} = activity <- Activity.get_by_id_with_object(id),
          {:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)},
-         %Object{data: %{"announcements" => announces}} <- Object.normalize(activity) do
+         %Object{data: %{"announcements" => announces, "id" => ap_id}} <-
+           Object.normalize(activity) do
+      announces =
+        "Announce"
+        |> Activity.Queries.by_type()
+        |> Ecto.Query.where([a], a.actor in ^announces)
+        # this is to use the index
+        |> Activity.Queries.by_object_id(ap_id)
+        |> Repo.all()
+        |> Enum.filter(&Visibility.visible_for_user?(&1, user))
+        |> Enum.map(& &1.actor)
+        |> Enum.uniq()
+
       users =
         User
         |> Ecto.Query.where([u], u.ap_id in ^announces)
diff --git a/test/web/mastodon_api/controllers/status_controller_test.exs b/test/web/mastodon_api/controllers/status_controller_test.exs
index 727a233e7..b648ad6ff 100644
--- a/test/web/mastodon_api/controllers/status_controller_test.exs
+++ b/test/web/mastodon_api/controllers/status_controller_test.exs
@@ -557,7 +557,7 @@ test "reblogs privately and returns the reblogged status", %{conn: conn} do
         |> post("/api/v1/statuses/#{activity.id}/reblog", %{"visibility" => "private"})
 
       assert %{
-               "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 0},
+               "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
                "reblogged" => true,
                "visibility" => "private"
              } = json_response(conn, 200)
@@ -1167,6 +1167,23 @@ test "does not return users who have reblogged the status but are blocked", %{
       assert Enum.empty?(response)
     end
 
+    test "does not return users who have reblogged the status privately", %{
+      conn: %{assigns: %{user: user}} = conn,
+      activity: activity
+    } do
+      other_user = insert(:user)
+
+      {:ok, _, _} = CommonAPI.repeat(activity.id, other_user, %{"visibility" => "private"})
+
+      response =
+        conn
+        |> assign(:user, user)
+        |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
+        |> json_response(:ok)
+
+      assert Enum.empty?(response)
+    end
+
     test "does not fail on an unauthenticated request", %{conn: conn, activity: activity} do
       other_user = insert(:user)
       {:ok, _, _} = CommonAPI.repeat(activity.id, other_user)