diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 4685f6d95..b8141146f 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -364,21 +364,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   @valid_visibilities ~w[direct unlisted public private]
 
-  defp restrict_visibility(query, %{visibility: "direct"}) do
-    public = "https://www.w3.org/ns/activitystreams#Public"
+  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)
+      )
 
-    from(
-      activity in query,
-      join: sender in User,
-      on: sender.ap_id == activity.actor,
-      # Are non-direct statuses with no to/cc possible?
-      where:
-        fragment(
-          "not (? && ?)",
-          [^public, sender.follower_address],
-          activity.recipients
-        )
-    )
+    Ecto.Adapters.SQL.to_sql(:all, Repo, query)
+
+    query
   end
 
   defp restrict_visibility(_query, %{visibility: visibility})
diff --git a/priv/repo/migrations/20190109152453_add_visibility_function.exs b/priv/repo/migrations/20190109152453_add_visibility_function.exs
new file mode 100644
index 000000000..3aadabcd7
--- /dev/null
+++ b/priv/repo/migrations/20190109152453_add_visibility_function.exs
@@ -0,0 +1,48 @@
+defmodule Pleroma.Repo.Migrations.AddVisibilityFunction do
+  use Ecto.Migration
+  @disable_ddl_transaction true
+
+  def up do
+    definition = """
+    create or replace function activity_visibility(actor varchar, recipients varchar[], data jsonb) returns varchar as $$
+    DECLARE
+      fa varchar;
+      public varchar := 'https://www.w3.org/ns/activitystreams#Public';
+    BEGIN
+      SELECT COALESCE(users.follower_address, '') into fa from users where users.ap_id = actor;
+
+      IF data->'to' ? public THEN
+        RETURN 'public';
+      ELSIF data->'cc' ? public THEN
+        RETURN 'unlisted';
+      ELSIF ARRAY[fa] && recipients THEN
+        RETURN 'private';
+      ELSIF not(ARRAY[fa, public] && recipients) THEN
+        RETURN 'direct';
+      ELSE
+        RETURN 'unknown';
+      END IF;
+    END;
+    $$ LANGUAGE plpgsql IMMUTABLE;
+    """
+
+    execute(definition)
+
+    create(
+      index(:activities, ["activity_visibility(actor, recipients, data)"],
+        name: :activities_visibility_index,
+        concurrently: true
+      )
+    )
+  end
+
+  def down do
+    drop(
+      index(:activities, ["activity_visibility(actor, recipients, data)"],
+        name: :activities_visibility_index
+      )
+    )
+
+    execute("drop function activity_visibility(actor varchar, recipients varchar[], data jsonb)")
+  end
+end
diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs
index 2453998ad..47aa5a56f 100644
--- a/test/web/activity_pub/activity_pub_test.exs
+++ b/test/web/activity_pub/activity_pub_test.exs
@@ -18,6 +18,42 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     :ok
   end
 
+  describe "fetching restricted by visibility" do
+    test "it restricts by the appropriate visibility" do
+      user = insert(:user)
+
+      {:ok, public_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "public"})
+
+      {:ok, direct_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"})
+
+      {:ok, unlisted_activity} =
+        CommonAPI.post(user, %{"status" => ".", "visibility" => "unlisted"})
+
+      {:ok, private_activity} =
+        CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
+
+      activities =
+        ActivityPub.fetch_activities([], %{:visibility => "direct", "actor_id" => user.ap_id})
+
+      assert activities == [direct_activity]
+
+      activities =
+        ActivityPub.fetch_activities([], %{:visibility => "unlisted", "actor_id" => user.ap_id})
+
+      assert activities == [unlisted_activity]
+
+      activities =
+        ActivityPub.fetch_activities([], %{:visibility => "private", "actor_id" => user.ap_id})
+
+      assert activities == [private_activity]
+
+      activities =
+        ActivityPub.fetch_activities([], %{:visibility => "public", "actor_id" => user.ap_id})
+
+      assert activities == [public_activity]
+    end
+  end
+
   describe "building a user from his ap id" do
     test "it returns a user" do
       user_id = "http://mastodon.example.org/users/admin"