Merge branch 'restrict-domain' into 'develop'
authorlain <lain@soykaf.club>
Wed, 4 Nov 2020 14:54:53 +0000 (14:54 +0000)
committerlain <lain@soykaf.club>
Wed, 4 Nov 2020 14:54:53 +0000 (14:54 +0000)
View a remote server's timeline

See merge request pleroma/pleroma!2713

12 files changed:
CHANGELOG.md
docs/API/differences_in_mastoapi_responses.md
lib/pleroma/activity/ir/topics.ex
lib/pleroma/web/activity_pub/activity_pub.ex
lib/pleroma/web/api_spec/operations/timeline_operation.ex
lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
lib/pleroma/web/streamer.ex
test/pleroma/activity/ir/topics_test.exs
test/pleroma/integration/mastodon_websocket_test.exs
test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs
test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs
test/pleroma/web/streamer_test.exs

index 8c5a9f9dcf13a2d59e078cb868eac378605ec1c4..73c2e13222e12393b9db55b42ba80a803d9e516b 100644 (file)
@@ -16,6 +16,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - App metrics: ability to restrict access to specified IP whitelist.
 - Account backup
 - Configuration: Add `:instance, autofollowing_nicknames` setting to provide a way to make accounts automatically follow new users that register on the local Pleroma instance.
+- Ability to view remote timelines, with ex. `/api/v1/timelines/public?instance=lain.com` and streams `public:remote` and `public:remote:media`
 
 ### Changed
 
index 38865dc68a8b4393a7c40d23eceacebf1a7bf434..bb1000b0bd893bde927d98c1fc1d3ae76375c941 100644 (file)
@@ -9,9 +9,13 @@ Pleroma uses 128-bit ids as opposed to Mastodon's 64 bits. However just like Mas
 ## Timelines
 
 Adding the parameter `with_muted=true` to the timeline queries will also return activities by muted (not by blocked!) users.
+
 Adding the parameter `exclude_visibilities` to the timeline queries will exclude the statuses with the given visibilities. The parameter accepts an array of visibility types (`public`, `unlisted`, `private`, `direct`), e.g., `exclude_visibilities[]=direct&exclude_visibilities[]=private`.
+
 Adding the parameter `reply_visibility` to the public and home timelines queries will filter replies. Possible values: without parameter (default) shows all replies, `following` - replies directed to you or users you follow, `self` - replies directed to you.
 
+Adding the parameter `instance=lain.com` to the public timeline will show only statuses originating from `lain.com` (or any remote instance).
+
 ## Statuses
 
 - `visibility`: has an additional possible value `list`
@@ -249,6 +253,8 @@ Has these additional fields under the `pleroma` object:
 
 There is an additional `user:pleroma_chat` stream. Incoming chat messages will make the current chat be sent to this `user` stream. The `event` of an incoming chat message is `pleroma:chat_update`. The payload is the updated chat with the incoming chat message in the `last_message` field.
 
+For viewing remote server timelines, there are `public:remote` and `public:remote:media` streams. Each of these accept a parameter like `?instance=lain.com`.
+
 ## Not implemented
 
 Pleroma is generally compatible with the Mastodon 2.7.2 API, but some newer features and non-essential features are omitted. These features usually return an HTTP 200 status code, but with an empty response. While they may be added in the future, they are considered low priority.
index 9e65bedade120e9daecc35fa4bfc132fdabc2fbb..fe2e8cb5c900d7b84f0e92bc1ba42a74c85d685b 100644 (file)
@@ -40,7 +40,8 @@ defmodule Pleroma.Activity.Ir.Topics do
   end
 
   defp item_creation_tags(tags, object, %{data: %{"type" => "Create"}} = activity) do
-    tags ++ hashtags_to_topics(object) ++ attachment_topics(object, activity)
+    tags ++
+      remote_topics(activity) ++ hashtags_to_topics(object) ++ attachment_topics(object, activity)
   end
 
   defp item_creation_tags(tags, _, _) do
@@ -55,9 +56,19 @@ defmodule Pleroma.Activity.Ir.Topics do
 
   defp hashtags_to_topics(_), do: []
 
+  defp remote_topics(%{local: true}), do: []
+
+  defp remote_topics(%{actor: actor}) when is_binary(actor),
+    do: ["public:remote:" <> URI.parse(actor).host]
+
+  defp remote_topics(_), do: []
+
   defp attachment_topics(%{data: %{"attachment" => []}}, _act), do: []
 
   defp attachment_topics(_object, %{local: true}), do: ["public:media", "public:local:media"]
 
+  defp attachment_topics(_object, %{actor: actor}) when is_binary(actor),
+    do: ["public:media", "public:remote:media:" <> URI.parse(actor).host]
+
   defp attachment_topics(_object, _act), do: ["public:media"]
 end
index 13869f89788f4642a15e9cae26d2ca3fb903b15e..d8f685d38112a88bdb9065a06ad8fa1166507e66 100644 (file)
@@ -937,16 +937,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   defp restrict_muted_reblogs(query, _), do: query
 
-  defp restrict_instance(query, %{instance: instance}) do
-    users =
-      from(
-        u in User,
-        select: u.ap_id,
-        where: fragment("? LIKE ?", u.nickname, ^"%@#{instance}")
-      )
-      |> Repo.all()
-
-    from(activity in query, where: activity.actor in ^users)
+  defp restrict_instance(query, %{instance: instance}) when is_binary(instance) do
+    from(
+      activity in query,
+      where: fragment("split_part(actor::text, '/'::text, 3) = ?", ^instance)
+    )
   end
 
   defp restrict_instance(query, _), do: query
index 1b5ad796fa08951b0fbf8e80e1fce253b886d30c..95720df9f38df9946c0c50dfae070fd25b629d85 100644 (file)
@@ -59,6 +59,7 @@ defmodule Pleroma.Web.ApiSpec.TimelineOperation do
       security: [%{"oAuth" => ["read:statuses"]}],
       parameters: [
         local_param(),
+        instance_param(),
         only_media_param(),
         with_muted_param(),
         exclude_visibilities_param(),
@@ -158,6 +159,15 @@ defmodule Pleroma.Web.ApiSpec.TimelineOperation do
     )
   end
 
+  defp instance_param do
+    Operation.parameter(
+      :instance,
+      :query,
+      %Schema{type: :string},
+      "Show only statuses from the given domain"
+    )
+  end
+
   defp with_muted_param do
     Operation.parameter(:with_muted, :query, BooleanLike, "Include activities by muted users")
   end
index 7a5c80e013e288b6e250c5340de7f4e540532c85..ac96520a3749cf2c8c96536337d93bda5c5e489a 100644 (file)
@@ -111,6 +111,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
         |> Map.put(:blocking_user, user)
         |> Map.put(:muting_user, user)
         |> Map.put(:reply_filtering_user, user)
+        |> Map.put(:instance, params[:instance])
         |> ActivityPub.fetch_public_activities()
 
       conn
index d618dfe54a93b74bf6fd9bb81b7151893b591500..71fe27c894db72f86641e41d401851c6762b3d68 100644 (file)
@@ -57,6 +57,15 @@ defmodule Pleroma.Web.Streamer do
     {:ok, "hashtag:" <> tag}
   end
 
+  # Allow remote instance streams.
+  def get_topic("public:remote", _user, _oauth_token, %{"instance" => instance} = _params) do
+    {:ok, "public:remote:" <> instance}
+  end
+
+  def get_topic("public:remote:media", _user, _oauth_token, %{"instance" => instance} = _params) do
+    {:ok, "public:remote:media:" <> instance}
+  end
+
   # Expand user streams.
   def get_topic(
         stream,
index 4ddcea1ec04f39a81b2598f3041379ad3d840da5..5e5c2f8dac7d48138931187a147c0685084a9782 100644 (file)
@@ -97,6 +97,20 @@ defmodule Pleroma.Activity.Ir.TopicsTest do
 
       refute Enum.member?(topics, "hashtag:2")
     end
+
+    test "non-local action produces public:remote topic", %{activity: activity} do
+      activity = %{activity | local: false, actor: "https://lain.com/users/lain"}
+      topics = Topics.get_activity_topics(activity)
+
+      assert Enum.member?(topics, "public:remote:lain.com")
+    end
+
+    test "local action doesn't produce public:remote topic", %{activity: activity} do
+      activity = %{activity | local: true, actor: "https://lain.com/users/lain"}
+      topics = Topics.get_activity_topics(activity)
+
+      refute Enum.member?(topics, "public:remote:lain.com")
+    end
   end
 
   describe "public visibility create events with attachments" do
@@ -128,6 +142,13 @@ defmodule Pleroma.Activity.Ir.TopicsTest do
 
       refute Enum.member?(topics, "public:local:media")
     end
+
+    test "non-local action produces public:remote:media topic", %{activity: activity} do
+      activity = %{activity | local: false, actor: "https://lain.com/users/lain"}
+      topics = Topics.get_activity_topics(activity)
+
+      assert Enum.member?(topics, "public:remote:media:lain.com")
+    end
   end
 
   describe "non-public visibility" do
index 0f2e6cc2b0a1169e84cdb62fc55523133dc0013d..bb8e795b723c5f1bf7e31aeee47a901e8cbe9e03 100644 (file)
@@ -49,6 +49,7 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do
   test "allows public streams without authentication" do
     assert {:ok, _} = start_socket("?stream=public")
     assert {:ok, _} = start_socket("?stream=public:local")
+    assert {:ok, _} = start_socket("?stream=public:remote&instance=lain.com")
     assert {:ok, _} = start_socket("?stream=hashtag&tag=lain")
   end
 
index 74140b7bc709c60be1f61e705c3866b28c525144..c06ae55cae2c7580c3850ab5cd8f12060b52bf57 100644 (file)
@@ -844,8 +844,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
   describe "instances" do
     test "GET /instances/:instance/statuses", %{conn: conn} do
-      user = insert(:user, local: false, nickname: "archaeme@archae.me")
-      user2 = insert(:user, local: false, nickname: "test@test.com")
+      user = insert(:user, local: false, ap_id: "https://archae.me/users/archaeme")
+      user2 = insert(:user, local: false, ap_id: "https://test.com/users/test")
       insert_pair(:note_activity, user: user)
       activity = insert(:note_activity, user: user2)
 
index 9f1ee04249db059c4524c076246e8e5ee2cd09f0..4c08ad60aa0b90a72a7c69e4af36efa24f60f95c 100644 (file)
@@ -147,6 +147,18 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do
       activities = json_response_and_validate_schema(res_conn, 200)
       [%{"id" => ^activity_id}] = activities
     end
+
+    test "can be filtered by instance", %{conn: conn} do
+      user = insert(:user, ap_id: "https://lain.com/users/lain")
+      insert(:note_activity, local: false)
+      insert(:note_activity, local: false)
+
+      {:ok, _} = CommonAPI.post(user, %{status: "test"})
+
+      conn = get(conn, "/api/v1/timelines/public?instance=lain.com")
+
+      assert length(json_response_and_validate_schema(conn, :ok)) == 1
+    end
   end
 
   defp local_and_remote_activities do
index 395016da2a8eb9f11d962fe37bf2b7aa3a2fe4af..0d89e01d0092a1cba681770bad5df68eee63cb88 100644 (file)
@@ -29,6 +29,14 @@ defmodule Pleroma.Web.StreamerTest do
       assert {:ok, "public:local:media"} = Streamer.get_topic("public:local:media", nil, nil)
     end
 
+    test "allows instance streams" do
+      assert {:ok, "public:remote:lain.com"} =
+               Streamer.get_topic("public:remote", nil, nil, %{"instance" => "lain.com"})
+
+      assert {:ok, "public:remote:media:lain.com"} =
+               Streamer.get_topic("public:remote:media", nil, nil, %{"instance" => "lain.com"})
+    end
+
     test "allows hashtag streams" do
       assert {:ok, "hashtag:cofe"} = Streamer.get_topic("hashtag", nil, nil, %{"tag" => "cofe"})
     end