From 080e1aa70e4af4e9cdc0589f28648468bf116d6b Mon Sep 17 00:00:00 2001
From: Maksim Pechnikov <parallel588@gmail.com>
Date: Mon, 3 Jun 2019 16:04:39 +0300
Subject: [PATCH 01/13] add option skip_thread_containment

---
 config/config.exs                            |  3 +-
 docs/config.md                               |  1 +
 lib/pleroma/user/info.ex                     |  5 +-
 lib/pleroma/web/activity_pub/activity_pub.ex |  7 +-
 lib/pleroma/web/streamer.ex                  | 25 +++++--
 test/web/activity_pub/visibilty_test.exs     | 43 +++++++++++
 test/web/streamer_test.exs                   | 78 ++++++++++++++++++++
 7 files changed, 150 insertions(+), 12 deletions(-)

diff --git a/config/config.exs b/config/config.exs
index 68168b279..ada4fb48d 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -237,7 +237,8 @@ config :pleroma, :instance,
   max_report_comment_size: 1000,
   safe_dm_mentions: false,
   healthcheck: false,
-  remote_post_retention_days: 90
+  remote_post_retention_days: 90,
+  skip_thread_containment: false
 
 config :pleroma, :app_account_creation, enabled: true, max_requests: 25, interval: 1800
 
diff --git a/docs/config.md b/docs/config.md
index 67b062fe9..fbb9079e6 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -105,6 +105,7 @@ config :pleroma, Pleroma.Emails.Mailer,
 * `safe_dm_mentions`: If set to true, only mentions at the beginning of a post will be used to address people in direct messages. This is to prevent accidental mentioning of people when talking about them (e.g. "@friend hey i really don't like @enemy"). (Default: `false`)
 * `healthcheck`: if set to true, system data will be shown on ``/api/pleroma/healthcheck``.
 * `remote_post_retention_days`: the default amount of days to retain remote posts when pruning the database
+* `skip_thread_containment`: Skip filter out broken threads. the default is `false`.
 
 ## :app_account_creation
 REST API for creating an account settings
diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex
index ef506c8da..d1fb4fe75 100644
--- a/lib/pleroma/user/info.ex
+++ b/lib/pleroma/user/info.ex
@@ -49,6 +49,8 @@ defmodule Pleroma.User.Info do
       default: %{"remote" => true, "local" => true, "followers" => true, "follows" => true}
     )
 
+    field(:skip_thread_containment, :boolean, default: false)
+
     # Found in the wild
     # ap_id -> Where is this used?
     # bio -> Where is this used?
@@ -208,7 +210,8 @@ defmodule Pleroma.User.Info do
       :hide_followers,
       :hide_favorites,
       :background,
-      :show_role
+      :show_role,
+      :skip_thread_containment
     ])
   end
 
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 8add62406..f121ef01b 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -4,6 +4,7 @@
 
 defmodule Pleroma.Web.ActivityPub.ActivityPub do
   alias Pleroma.Activity
+  alias Pleroma.Config
   alias Pleroma.Conversation
   alias Pleroma.Notification
   alias Pleroma.Object
@@ -73,7 +74,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
   end
 
   defp check_remote_limit(%{"object" => %{"content" => content}}) when not is_nil(content) do
-    limit = Pleroma.Config.get([:instance, :remote_limit])
+    limit = Config.get([:instance, :remote_limit])
     String.length(content) <= limit
   end
 
@@ -399,8 +400,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
   end
 
   def block(blocker, blocked, activity_id \\ nil, local \\ true) do
-    outgoing_blocks = Pleroma.Config.get([:activitypub, :outgoing_blocks])
-    unfollow_blocked = Pleroma.Config.get([:activitypub, :unfollow_blocked])
+    outgoing_blocks = Config.get([:activitypub, :outgoing_blocks])
+    unfollow_blocked = Config.get([:activitypub, :unfollow_blocked])
 
     if unfollow_blocked do
       follow_activity = fetch_latest_follow(blocker, blocked)
diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex
index 133decfc4..a23f80f26 100644
--- a/lib/pleroma/web/streamer.ex
+++ b/lib/pleroma/web/streamer.ex
@@ -6,6 +6,7 @@ defmodule Pleroma.Web.Streamer do
   use GenServer
   require Logger
   alias Pleroma.Activity
+  alias Pleroma.Config
   alias Pleroma.Conversation.Participation
   alias Pleroma.Notification
   alias Pleroma.Object
@@ -224,11 +225,10 @@ defmodule Pleroma.Web.Streamer do
         mutes = user.info.mutes || []
         reblog_mutes = user.info.muted_reblogs || []
 
-        parent = Object.normalize(item)
-
-        unless is_nil(parent) or item.actor in blocks or item.actor in mutes or
-                 item.actor in reblog_mutes or not ActivityPub.contain_activity(item, user) or
-                 parent.data["actor"] in blocks or parent.data["actor"] in mutes do
+        with parent when not is_nil(parent) <- Object.normalize(item),
+             true <- Enum.all?([blocks, mutes, reblog_mutes], &(item.actor not in &1)),
+             true <- Enum.all?([blocks, mutes], &(parent.data["actor"] not in &1)),
+             true <- thread_containment(item, user) do
           send(socket.transport_pid, {:text, represent_update(item, user)})
         end
       else
@@ -264,8 +264,8 @@ defmodule Pleroma.Web.Streamer do
         blocks = user.info.blocks || []
         mutes = user.info.mutes || []
 
-        unless item.actor in blocks or item.actor in mutes or
-                 not ActivityPub.contain_activity(item, user) do
+        with true <- Enum.all?([blocks, mutes], &(item.actor not in &1)),
+             true <- thread_containment(item, user) do
           send(socket.transport_pid, {:text, represent_update(item, user)})
         end
       else
@@ -279,4 +279,15 @@ defmodule Pleroma.Web.Streamer do
   end
 
   defp internal_topic(topic, _), do: topic
+
+  @spec thread_containment(Activity.t(), User.t()) :: boolean()
+  defp thread_containment(_activity, %User{info: %{skip_thread_containment: true}}), do: true
+
+  defp thread_containment(activity, user) do
+    if Config.get([:instance, :skip_thread_containment]) do
+      true
+    else
+      ActivityPub.contain_activity(activity, user)
+    end
+  end
 end
diff --git a/test/web/activity_pub/visibilty_test.exs b/test/web/activity_pub/visibilty_test.exs
index 466d980dc..e24df3cab 100644
--- a/test/web/activity_pub/visibilty_test.exs
+++ b/test/web/activity_pub/visibilty_test.exs
@@ -1,6 +1,7 @@
 defmodule Pleroma.Web.ActivityPub.VisibilityTest do
   use Pleroma.DataCase
 
+  alias Pleroma.Activity
   alias Pleroma.Web.ActivityPub.Visibility
   alias Pleroma.Web.CommonAPI
   import Pleroma.Factory
@@ -121,4 +122,46 @@ defmodule Pleroma.Web.ActivityPub.VisibilityTest do
   test "get_visibility with directMessage flag" do
     assert Visibility.get_visibility(%{data: %{"directMessage" => true}}) == "direct"
   end
+
+  describe "entire_thread_visible_for_user?/2" do
+    test "returns false if not found activity", %{user: user} do
+      refute Visibility.entire_thread_visible_for_user?(%Activity{}, user)
+    end
+
+    test "returns true if activity hasn't 'Create' type", %{user: user} do
+      activity = insert(:like_activity)
+      assert Visibility.entire_thread_visible_for_user?(activity, user)
+    end
+
+    test "returns false when invalid recipients", %{user: user} do
+      author = insert(:user)
+
+      activity =
+        insert(:note_activity,
+          note:
+            insert(:note,
+              user: author,
+              data: %{"to" => ["test-user"]}
+            )
+        )
+
+      refute Visibility.entire_thread_visible_for_user?(activity, user)
+    end
+
+    test "returns true if user following to author" do
+      author = insert(:user)
+      user = insert(:user, following: [author.ap_id])
+
+      activity =
+        insert(:note_activity,
+          note:
+            insert(:note,
+              user: author,
+              data: %{"to" => [user.ap_id]}
+            )
+        )
+
+      assert Visibility.entire_thread_visible_for_user?(activity, user)
+    end
+  end
 end
diff --git a/test/web/streamer_test.exs b/test/web/streamer_test.exs
index bfe18cb7f..c18b9f9fe 100644
--- a/test/web/streamer_test.exs
+++ b/test/web/streamer_test.exs
@@ -11,6 +11,16 @@ defmodule Pleroma.Web.StreamerTest do
   alias Pleroma.Web.Streamer
   import Pleroma.Factory
 
+  setup do
+    skip_thread_containment = Pleroma.Config.get([:instance, :skip_thread_containment])
+
+    on_exit(fn ->
+      Pleroma.Config.put([:instance, :skip_thread_containment], skip_thread_containment)
+    end)
+
+    :ok
+  end
+
   test "it sends to public" do
     user = insert(:user)
     other_user = insert(:user)
@@ -68,6 +78,74 @@ defmodule Pleroma.Web.StreamerTest do
     Task.await(task)
   end
 
+  describe "thread_containment" do
+    test "it doesn't send to user if recipients invalid and thread containment is enabled" do
+      Pleroma.Config.put([:instance, :skip_thread_containment], false)
+      author = insert(:user)
+      user = insert(:user, following: [author.ap_id])
+
+      activity =
+        insert(:note_activity,
+          note:
+            insert(:note,
+              user: author,
+              data: %{"to" => ["TEST-FFF"]}
+            )
+        )
+
+      task = Task.async(fn -> refute_receive {:text, _}, 1_000 end)
+      fake_socket = %{transport_pid: task.pid, assigns: %{user: user}}
+      topics = %{"public" => [fake_socket]}
+      Streamer.push_to_socket(topics, "public", activity)
+
+      Task.await(task)
+    end
+
+    test "it sends message if recipients invalid and thread containment is disabled" do
+      Pleroma.Config.put([:instance, :skip_thread_containment], true)
+      author = insert(:user)
+      user = insert(:user, following: [author.ap_id])
+
+      activity =
+        insert(:note_activity,
+          note:
+            insert(:note,
+              user: author,
+              data: %{"to" => ["TEST-FFF"]}
+            )
+        )
+
+      task = Task.async(fn -> assert_receive {:text, _}, 1_000 end)
+      fake_socket = %{transport_pid: task.pid, assigns: %{user: user}}
+      topics = %{"public" => [fake_socket]}
+      Streamer.push_to_socket(topics, "public", activity)
+
+      Task.await(task)
+    end
+
+    test "it sends message if recipients invalid and thread containment is enabled but user's thread containment is disabled" do
+      Pleroma.Config.put([:instance, :skip_thread_containment], false)
+      author = insert(:user)
+      user = insert(:user, following: [author.ap_id], info: %{skip_thread_containment: true})
+
+      activity =
+        insert(:note_activity,
+          note:
+            insert(:note,
+              user: author,
+              data: %{"to" => ["TEST-FFF"]}
+            )
+        )
+
+      task = Task.async(fn -> assert_receive {:text, _}, 1_000 end)
+      fake_socket = %{transport_pid: task.pid, assigns: %{user: user}}
+      topics = %{"public" => [fake_socket]}
+      Streamer.push_to_socket(topics, "public", activity)
+
+      Task.await(task)
+    end
+  end
+
   test "it doesn't send to blocked users" do
     user = insert(:user)
     blocked_user = insert(:user)

From f13d6c7f78cfae4005b351248ce3e9069abf93e2 Mon Sep 17 00:00:00 2001
From: Maksim Pechnikov <parallel588@gmail.com>
Date: Mon, 3 Jun 2019 21:02:02 +0300
Subject: [PATCH 02/13] update api to set skip_thread_containment

---
 docs/api/differences_in_mastoapi_responses.md |  1 +
 .../mastodon_api/mastodon_api_controller.ex   | 10 +++++++-
 .../web/mastodon_api/views/account_view.ex    |  3 ++-
 .../web/twitter_api/twitter_api_controller.ex | 10 +++++++-
 .../web/twitter_api/views/user_view.ex        |  3 ++-
 .../mastodon_api_controller_test.exs          | 13 ++++++++++
 .../twitter_api_controller_test.exs           | 25 ++++++++++++++++++-
 7 files changed, 60 insertions(+), 5 deletions(-)

diff --git a/docs/api/differences_in_mastoapi_responses.md b/docs/api/differences_in_mastoapi_responses.md
index 36b47608e..ed156836d 100644
--- a/docs/api/differences_in_mastoapi_responses.md
+++ b/docs/api/differences_in_mastoapi_responses.md
@@ -80,6 +80,7 @@ Additional parameters can be added to the JSON body/Form data:
 - `hide_favorites` - if true, user's favorites timeline will be hidden
 - `show_role` - if true, user's role (e.g admin, moderator) will be exposed to anyone in the API
 - `default_scope` - the scope returned under `privacy` key in Source subentity
+- `skip_thread_containment` - if true, skip filtering out broken threads
 
 ## Authentication
 
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index 0d81fb840..52eed51c1 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -117,7 +117,15 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
       |> Enum.dedup()
 
     info_params =
-      [:no_rich_text, :locked, :hide_followers, :hide_follows, :hide_favorites, :show_role]
+      [
+        :no_rich_text,
+        :locked,
+        :hide_followers,
+        :hide_follows,
+        :hide_favorites,
+        :show_role,
+        :skip_thread_containment
+      ]
       |> Enum.reduce(%{}, fn key, acc ->
         add_if_present(acc, params, to_string(key), key, fn value ->
           {:ok, ControllerHelper.truthy_param?(value)}
diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex
index b82d3319b..7b7e58eac 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -124,7 +124,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
         hide_followers: user.info.hide_followers,
         hide_follows: user.info.hide_follows,
         hide_favorites: user.info.hide_favorites,
-        relationship: relationship
+        relationship: relationship,
+        skip_thread_containment: user.info.skip_thread_containment
       }
     }
     |> maybe_put_role(user, opts[:for])
diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex
index 1b6b33e69..6cf107d17 100644
--- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex
+++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex
@@ -632,7 +632,15 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
 
   defp build_info_cng(user, params) do
     info_params =
-      ["no_rich_text", "locked", "hide_followers", "hide_follows", "hide_favorites", "show_role"]
+      [
+        "no_rich_text",
+        "locked",
+        "hide_followers",
+        "hide_follows",
+        "hide_favorites",
+        "show_role",
+        "skip_thread_containment"
+      ]
       |> Enum.reduce(%{}, fn key, res ->
         if value = params[key] do
           Map.put(res, key, value == "true")
diff --git a/lib/pleroma/web/twitter_api/views/user_view.ex b/lib/pleroma/web/twitter_api/views/user_view.ex
index f0a4ddbd3..84875613a 100644
--- a/lib/pleroma/web/twitter_api/views/user_view.ex
+++ b/lib/pleroma/web/twitter_api/views/user_view.ex
@@ -118,7 +118,8 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
         "pleroma" =>
           %{
             "confirmation_pending" => user_info.confirmation_pending,
-            "tags" => user.tags
+            "tags" => user.tags,
+            "skip_thread_containment" => user.info.skip_thread_containment
           }
           |> maybe_with_activation_status(user, for_user)
       }
diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs
index f5f87d8af..587df0481 100644
--- a/test/web/mastodon_api/mastodon_api_controller_test.exs
+++ b/test/web/mastodon_api/mastodon_api_controller_test.exs
@@ -2378,6 +2378,19 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
       assert user["pleroma"]["hide_followers"] == true
     end
 
+    test "updates the user's skip_thread_containment option", %{conn: conn} do
+      user = insert(:user)
+
+      response =
+        conn
+        |> assign(:user, user)
+        |> patch("/api/v1/accounts/update_credentials", %{skip_thread_containment: "true"})
+        |> json_response(200)
+
+      assert response["pleroma"]["skip_thread_containment"] == true
+      assert refresh_record(user).info.skip_thread_containment
+    end
+
     test "updates the user's hide_follows status", %{conn: conn} do
       user = insert(:user)
 
diff --git a/test/web/twitter_api/twitter_api_controller_test.exs b/test/web/twitter_api/twitter_api_controller_test.exs
index bcd0f522d..8187ffd0e 100644
--- a/test/web/twitter_api/twitter_api_controller_test.exs
+++ b/test/web/twitter_api/twitter_api_controller_test.exs
@@ -1495,7 +1495,7 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
           "hide_follows" => "false"
         })
 
-      user = Repo.get!(User, user.id)
+      user = refresh_record(user)
       assert user.info.hide_follows == false
       assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user})
     end
@@ -1548,6 +1548,29 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
       assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user})
     end
 
+    test "it sets and un-sets skip_thread_containment", %{conn: conn} do
+      user = insert(:user)
+
+      response =
+        conn
+        |> assign(:user, user)
+        |> post("/api/account/update_profile.json", %{"skip_thread_containment" => "true"})
+        |> json_response(200)
+
+      assert response["pleroma"]["skip_thread_containment"] == true
+      user = refresh_record(user)
+      assert user.info.skip_thread_containment
+
+      response =
+        conn
+        |> assign(:user, user)
+        |> post("/api/account/update_profile.json", %{"skip_thread_containment" => "false"})
+        |> json_response(200)
+
+      assert response["pleroma"]["skip_thread_containment"] == false
+      refute refresh_record(user).info.skip_thread_containment
+    end
+
     test "it locks an account", %{conn: conn} do
       user = insert(:user)
 

From 46c7e16512a4e51e2c062b9985bd04fb76487a28 Mon Sep 17 00:00:00 2001
From: Maksim Pechnikov <parallel588@gmail.com>
Date: Mon, 3 Jun 2019 21:05:45 +0300
Subject: [PATCH 03/13] updated changelog

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index ff1fff876..419dcf3b7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -45,6 +45,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - OAuth: added job to clean expired access tokens
 - MRF: Support for rejecting reports from specific instances (`mrf_simple`)
 - MRF: Support for stripping avatars and banner images from specific instances (`mrf_simple`)
+- Configuration: `skip_thread_containment` option
 
 ### Changed
 - **Breaking:** Configuration: move from Pleroma.Mailer to Pleroma.Emails.Mailer

From 64ada7f960eb45d5e06d431c0c27be1014106ff9 Mon Sep 17 00:00:00 2001
From: Maksim Pechnikov <parallel588@gmail.com>
Date: Mon, 3 Jun 2019 22:51:14 +0300
Subject: [PATCH 04/13] fix tests

---
 test/web/mastodon_api/account_view_test.exs   |  9 ++++++---
 test/web/twitter_api/views/user_view_test.exs | 12 ++++++++----
 2 files changed, 14 insertions(+), 7 deletions(-)

diff --git a/test/web/mastodon_api/account_view_test.exs b/test/web/mastodon_api/account_view_test.exs
index aaf2261bb..66ae8b4bb 100644
--- a/test/web/mastodon_api/account_view_test.exs
+++ b/test/web/mastodon_api/account_view_test.exs
@@ -67,7 +67,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
         hide_favorites: true,
         hide_followers: false,
         hide_follows: false,
-        relationship: %{}
+        relationship: %{},
+        skip_thread_containment: false
       }
     }
 
@@ -132,7 +133,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
         hide_favorites: true,
         hide_followers: false,
         hide_follows: false,
-        relationship: %{}
+        relationship: %{},
+        skip_thread_containment: false
       }
     }
 
@@ -233,7 +235,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
           domain_blocking: false,
           showing_reblogs: true,
           endorsed: false
-        }
+        },
+        skip_thread_containment: false
       }
     }
 
diff --git a/test/web/twitter_api/views/user_view_test.exs b/test/web/twitter_api/views/user_view_test.exs
index 74526673c..9870c17c0 100644
--- a/test/web/twitter_api/views/user_view_test.exs
+++ b/test/web/twitter_api/views/user_view_test.exs
@@ -99,7 +99,8 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do
       "fields" => [],
       "pleroma" => %{
         "confirmation_pending" => false,
-        "tags" => []
+        "tags" => [],
+        "skip_thread_containment" => false
       },
       "rights" => %{"admin" => false, "delete_others_notice" => false},
       "role" => "member"
@@ -152,7 +153,8 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do
       "fields" => [],
       "pleroma" => %{
         "confirmation_pending" => false,
-        "tags" => []
+        "tags" => [],
+        "skip_thread_containment" => false
       },
       "rights" => %{"admin" => false, "delete_others_notice" => false},
       "role" => "member"
@@ -197,7 +199,8 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do
       "fields" => [],
       "pleroma" => %{
         "confirmation_pending" => false,
-        "tags" => []
+        "tags" => [],
+        "skip_thread_containment" => false
       },
       "rights" => %{"admin" => false, "delete_others_notice" => false},
       "role" => "member"
@@ -279,7 +282,8 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do
       "fields" => [],
       "pleroma" => %{
         "confirmation_pending" => false,
-        "tags" => []
+        "tags" => [],
+        "skip_thread_containment" => false
       },
       "rights" => %{"admin" => false, "delete_others_notice" => false},
       "role" => "member"

From 96121315f3e9aebc57d36a669fc4003905cd0ba6 Mon Sep 17 00:00:00 2001
From: Maksim Pechnikov <parallel588@gmail.com>
Date: Tue, 4 Jun 2019 12:41:24 +0300
Subject: [PATCH 05/13] fix merge

---
 docs/api/differences_in_mastoapi_responses.md | 1 -
 1 file changed, 1 deletion(-)

diff --git a/docs/api/differences_in_mastoapi_responses.md b/docs/api/differences_in_mastoapi_responses.md
index 88a43de38..623d4fbf5 100644
--- a/docs/api/differences_in_mastoapi_responses.md
+++ b/docs/api/differences_in_mastoapi_responses.md
@@ -90,7 +90,6 @@ Pleroma has mechanism that allows frontends to save blobs of json for each user
 The parameter should have a form of `{frontend_name: {...}}`, with `frontend_name` identifying your type of client, e.g. `pleroma_fe`. It will overwrite everything under this property, but will not overwrite other frontend's settings.
 
 This information is returned in the `verify_credentials` endpoint.
->>>>>>> develop
 
 ## Authentication
 

From 29b022bb597f36621ef3f5056c5ca2b7f0c8edbe Mon Sep 17 00:00:00 2001
From: rinpatch <rinpatch@sdf.org>
Date: Tue, 4 Jun 2019 12:42:10 +0300
Subject: [PATCH 06/13] Restrict `get_existing_votes` to only get Create
 activities

---
 lib/pleroma/web/activity_pub/utils.ex | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
index b8159e9e5..faae7e747 100644
--- a/lib/pleroma/web/activity_pub/utils.ex
+++ b/lib/pleroma/web/activity_pub/utils.ex
@@ -794,6 +794,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
     query =
       from(
         [activity, object: object] in Activity.with_preloaded_object(Activity),
+        where: fragment("(?)->>'type' = 'Create'", activity.data),
         where: fragment("(?)->>'actor' = ?", activity.data, ^actor),
         where:
           fragment(

From e74581a5c4c3aca68a304efb683dffed80d1337f Mon Sep 17 00:00:00 2001
From: lain <lain@soykaf.club>
Date: Tue, 4 Jun 2019 12:01:21 +0200
Subject: [PATCH 07/13] Emoji: Don't die when files are present in the emoji
 folder.

---
 lib/pleroma/emoji.ex | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex
index 7d12eff7f..de7fcc1ce 100644
--- a/lib/pleroma/emoji.ex
+++ b/lib/pleroma/emoji.ex
@@ -97,10 +97,22 @@ defmodule Pleroma.Emoji do
         # There was some other error
         Logger.error("Could not access the custom emoji directory #{emoji_dir_path}: #{e}")
 
-      {:ok, packs} ->
+      {:ok, results} ->
+        grouped = Enum.group_by(results, &File.dir?/1)
+        packs = grouped[true] || []
+        files = grouped[false] || []
+
         # Print the packs we've found
         Logger.info("Found emoji packs: #{Enum.join(packs, ", ")}")
 
+        if not Enum.empty?(files) do
+          Logger.warn(
+            "Found files in the emoji folder. These will be ignored, please move them to a subdirectory\nFound files: #{
+              Enum.join(files, ", ")
+            }"
+          )
+        end
+
         emojis =
           Enum.flat_map(
             packs,

From 17383861edf6c9d7308101182607e7ff9202af73 Mon Sep 17 00:00:00 2001
From: rinpatch <rinpatch@sdf.org>
Date: Tue, 4 Jun 2019 13:38:24 +0300
Subject: [PATCH 08/13] Fix CommonAPI.vote returning tuples inside of the
 activity array instead of just activities

---
 lib/pleroma/web/common_api/common_api.ex | 17 ++++++++++-------
 1 file changed, 10 insertions(+), 7 deletions(-)

diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex
index 5212d5ce5..ad3c03c55 100644
--- a/lib/pleroma/web/common_api/common_api.ex
+++ b/lib/pleroma/web/common_api/common_api.ex
@@ -132,13 +132,16 @@ defmodule Pleroma.Web.CommonAPI do
         Enum.map(choices, fn index ->
           answer_data = make_answer_data(user, object, Enum.at(options, index)["name"])
 
-          ActivityPub.create(%{
-            to: answer_data["to"],
-            actor: user,
-            context: object.data["context"],
-            object: answer_data,
-            additional: %{"cc" => answer_data["cc"]}
-          })
+          {:ok, activity} =
+            ActivityPub.create(%{
+              to: answer_data["to"],
+              actor: user,
+              context: object.data["context"],
+              object: answer_data,
+              additional: %{"cc" => answer_data["cc"]}
+            })
+
+          activity
         end)
 
       object = Object.get_cached_by_ap_id(object.data["id"])

From bbff7554de7f8c3965387fb3509728c1f2c2d04b Mon Sep 17 00:00:00 2001
From: rinpatch <rinpatch@sdf.org>
Date: Tue, 4 Jun 2019 13:47:53 +0300
Subject: [PATCH 09/13] Add tests for get_existing_votes

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

diff --git a/test/web/activity_pub/utils_test.exs b/test/web/activity_pub/utils_test.exs
index c57fae437..de741c64b 100644
--- a/test/web/activity_pub/utils_test.exs
+++ b/test/web/activity_pub/utils_test.exs
@@ -1,6 +1,7 @@
 defmodule Pleroma.Web.ActivityPub.UtilsTest do
   use Pleroma.DataCase
   alias Pleroma.Activity
+  alias Pleroma.Object
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.ActivityPub
   alias Pleroma.Web.ActivityPub.Utils
@@ -204,4 +205,46 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do
              ]
            }
   end
+
+  describe "get_existing_votes" do
+    test "fetches existing votes" do
+      user = insert(:user)
+      other_user = insert(:user)
+
+      {:ok, activity} =
+        CommonAPI.post(user, %{
+          "status" => "How do I pronounce LaTeX?",
+          "poll" => %{
+            "options" => ["laytekh", "lahtekh", "latex"],
+            "expires_in" => 20,
+            "multiple" => true
+          }
+        })
+
+      object = Object.normalize(activity)
+      {:ok, votes, object} = CommonAPI.vote(other_user, object, [0, 1])
+      assert Enum.sort(Utils.get_existing_votes(other_user.ap_id, object)) == Enum.sort(votes)
+    end
+
+    test "fetches only Create activities" do
+      user = insert(:user)
+      other_user = insert(:user)
+
+      {:ok, activity} =
+        CommonAPI.post(user, %{
+          "status" => "Are we living in a society?",
+          "poll" => %{
+            "options" => ["yes", "no"],
+            "expires_in" => 20
+          }
+        })
+
+      object = Object.normalize(activity)
+      {:ok, [vote], object} = CommonAPI.vote(other_user, object, [0])
+      vote_object = Object.normalize(vote)
+      {:ok, _activity, _object} = ActivityPub.like(user, vote_object)
+      [fetched_vote] = Utils.get_existing_votes(other_user.ap_id, object)
+      assert fetched_vote.id == vote.id
+    end
+  end
 end

From a3a7178b604d8bc589a8e3ac06abac094cce5e17 Mon Sep 17 00:00:00 2001
From: lain <lain@soykaf.club>
Date: Tue, 4 Jun 2019 13:58:36 +0200
Subject: [PATCH 10/13] Participations: Filter out participations without
 activities.

---
 lib/pleroma/conversation/participation.ex |  1 +
 test/conversation/participation_test.exs  | 13 +++++++++++++
 2 files changed, 14 insertions(+)

diff --git a/lib/pleroma/conversation/participation.ex b/lib/pleroma/conversation/participation.ex
index 2a11f9069..2c13c4b40 100644
--- a/lib/pleroma/conversation/participation.ex
+++ b/lib/pleroma/conversation/participation.ex
@@ -79,5 +79,6 @@ defmodule Pleroma.Conversation.Participation do
         | last_activity_id: activity_id
       }
     end)
+    |> Enum.filter(& &1.last_activity_id)
   end
 end
diff --git a/test/conversation/participation_test.exs b/test/conversation/participation_test.exs
index 568953b07..0e60bfca5 100644
--- a/test/conversation/participation_test.exs
+++ b/test/conversation/participation_test.exs
@@ -86,4 +86,17 @@ defmodule Pleroma.Conversation.ParticipationTest do
 
     assert participation_one.last_activity_id == activity_three.id
   end
+
+  test "Doesn't die when the conversation gets empty" do
+    user = insert(:user)
+
+    {:ok, activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"})
+    [participation] = Participation.for_user_with_last_activity_id(user)
+
+    assert participation.last_activity_id == activity.id
+
+    {:ok, _} = CommonAPI.delete(activity.id, user)
+
+    [] = Participation.for_user_with_last_activity_id(user)
+  end
 end

From 0acfcf6c522a82ca416b6664d588d3daa32a6fba Mon Sep 17 00:00:00 2001
From: Maksim Pechnikov <parallel588@gmail.com>
Date: Tue, 4 Jun 2019 15:04:36 +0300
Subject: [PATCH 11/13] update ActivityPub#fetch_activities_query

---
 lib/pleroma/web/activity_pub/activity_pub.ex | 32 +++++++++-----------
 1 file changed, 15 insertions(+), 17 deletions(-)

diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 8c4d0c15d..eefed5832 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -558,14 +558,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   defp restrict_visibility(query, %{visibility: visibility})
        when visibility in @valid_visibilities do
-    query =
-      from(
-        a in query,
-        where:
-          fragment("activity_visibility(?, ?, ?) = ?", a.actor, a.recipients, a.data, ^visibility)
-      )
-
-    query
+    from(
+      a in query,
+      where:
+        fragment("activity_visibility(?, ?, ?) = ?", a.actor, a.recipients, a.data, ^visibility)
+    )
   end
 
   defp restrict_visibility(_query, %{visibility: visibility})
@@ -575,17 +572,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   defp restrict_visibility(query, _visibility), do: query
 
-  defp restrict_thread_visibility(query, %{"user" => %User{ap_id: ap_id}}) do
-    query =
-      from(
-        a in query,
-        where: fragment("thread_visibility(?, (?)->>'id') = true", ^ap_id, a.data)
-      )
+  defp restrict_thread_visibility(query, _, %{skip_thread_containment: true} = _),
+    do: query
 
-    query
+  defp restrict_thread_visibility(query, %{"user" => %User{ap_id: ap_id}}, _) do
+    from(
+      a in query,
+      where: fragment("thread_visibility(?, (?)->>'id') = true", ^ap_id, a.data)
+    )
   end
 
-  defp restrict_thread_visibility(query, _), do: query
+  defp restrict_thread_visibility(query, _, _), do: query
 
   def fetch_user_activities(user, reading_user, params \\ %{}) do
     params =
@@ -863,6 +860,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   def fetch_activities_query(recipients, opts \\ %{}) do
     base_query = from(activity in Activity)
+    config = Enum.into(Config.get([:instance]), %{})
 
     base_query
     |> maybe_preload_objects(opts)
@@ -883,7 +881,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     |> restrict_muted(opts)
     |> restrict_media(opts)
     |> restrict_visibility(opts)
-    |> restrict_thread_visibility(opts)
+    |> restrict_thread_visibility(opts, config)
     |> restrict_replies(opts)
     |> restrict_reblogs(opts)
     |> restrict_pinned(opts)

From 1e7bb69a957c279eb75ed72cca779caa9d8f25ce Mon Sep 17 00:00:00 2001
From: Maksim Pechnikov <parallel588@gmail.com>
Date: Tue, 4 Jun 2019 15:20:24 +0300
Subject: [PATCH 12/13] update ActivityPub#fetch_activities_query

---
 lib/pleroma/web/activity_pub/activity_pub.ex | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index eefed5832..c0e3d1478 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -575,6 +575,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
   defp restrict_thread_visibility(query, _, %{skip_thread_containment: true} = _),
     do: query
 
+  defp restrict_thread_visibility(
+         query,
+         %{"user" => %User{info: %{skip_thread_containment: true}}},
+         _
+       ),
+       do: query
+
   defp restrict_thread_visibility(query, %{"user" => %User{ap_id: ap_id}}, _) do
     from(
       a in query,
@@ -860,7 +867,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   def fetch_activities_query(recipients, opts \\ %{}) do
     base_query = from(activity in Activity)
-    config = Enum.into(Config.get([:instance]), %{})
+
+    config = %{
+      skip_thread_containment: Config.get([:instance, :skip_thread_containment])
+    }
 
     base_query
     |> maybe_preload_objects(opts)

From b5e3b1e58ad9f4d8237821572e4d149eea881ff2 Mon Sep 17 00:00:00 2001
From: lain <lain@soykaf.club>
Date: Tue, 4 Jun 2019 16:44:56 +0200
Subject: [PATCH 13/13] Mix: Swallow git error messages during version number
 handling.

Otherwise sometimes a 'fatal' message will be printed, confusing users.
---
 mix.exs | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/mix.exs b/mix.exs
index df1a7ced4..9447a2e4f 100644
--- a/mix.exs
+++ b/mix.exs
@@ -157,7 +157,8 @@ defmodule Pleroma.Mixfile do
   #   * the mix environment if different than prod
   defp version(version) do
     {git_tag, git_pre_release} =
-      with {tag, 0} <- System.cmd("git", ["describe", "--tags", "--abbrev=0"]),
+      with {tag, 0} <-
+             System.cmd("git", ["describe", "--tags", "--abbrev=0"], stderr_to_stdout: true),
            tag = String.trim(tag),
            {describe, 0} <- System.cmd("git", ["describe", "--tags", "--abbrev=8"]),
            describe = String.trim(describe),