add license boilerplate to pleroma core
[akkoma] / lib / pleroma / web / streamer.ex
index d64e6c393b0ddb71428a5027541d6b5808e32227..05f877438dbd103a0238f7962a2eec7578b82615 100644 (file)
@@ -1,13 +1,16 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
 defmodule Pleroma.Web.Streamer do
   use GenServer
   require Logger
-  alias Pleroma.{User, Notification}
+  alias Pleroma.{User, Notification, Activity, Object, Repo}
+  alias Pleroma.Web.ActivityPub.ActivityPub
+
+  @keepalive_interval :timer.seconds(30)
 
   def start_link do
-    spawn(fn ->
-      Process.sleep(1000 * 30) # 30 seconds
-      GenServer.cast(__MODULE__, %{action: :ping})
-    end)
     GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
   end
 
@@ -23,41 +26,107 @@ defmodule Pleroma.Web.Streamer do
     GenServer.cast(__MODULE__, %{action: :stream, topic: topic, item: item})
   end
 
+  def init(args) do
+    spawn(fn ->
+      # 30 seconds
+      Process.sleep(@keepalive_interval)
+      GenServer.cast(__MODULE__, %{action: :ping})
+    end)
+
+    {:ok, args}
+  end
+
   def handle_cast(%{action: :ping}, topics) do
     Map.values(topics)
-    |> List.flatten
-    |> Enum.each(fn (socket) ->
+    |> List.flatten()
+    |> Enum.each(fn socket ->
       Logger.debug("Sending keepalive ping")
-      send socket.transport_pid, {:text, ""}
+      send(socket.transport_pid, {:text, ""})
     end)
+
     spawn(fn ->
-      Process.sleep(1000 * 30) # 30 seconds
+      # 30 seconds
+      Process.sleep(@keepalive_interval)
       GenServer.cast(__MODULE__, %{action: :ping})
     end)
+
+    {:noreply, topics}
+  end
+
+  def handle_cast(%{action: :stream, topic: "direct", item: item}, topics) do
+    recipient_topics =
+      User.get_recipients_from_activity(item)
+      |> Enum.map(fn %{id: id} -> "direct:#{id}" end)
+
+    Enum.each(recipient_topics || [], fn user_topic ->
+      Logger.debug("Trying to push direct message to #{user_topic}\n\n")
+      push_to_socket(topics, user_topic, item)
+    end)
+
+    {:noreply, topics}
+  end
+
+  def handle_cast(%{action: :stream, topic: "list", item: item}, topics) do
+    # filter the recipient list if the activity is not public, see #270.
+    recipient_lists =
+      case ActivityPub.is_public?(item) do
+        true ->
+          Pleroma.List.get_lists_from_activity(item)
+
+        _ ->
+          Pleroma.List.get_lists_from_activity(item)
+          |> Enum.filter(fn list ->
+            owner = Repo.get(User, list.user_id)
+
+            ActivityPub.visible_for_user?(item, owner)
+          end)
+      end
+
+    recipient_topics =
+      recipient_lists
+      |> Enum.map(fn %{id: id} -> "list:#{id}" end)
+
+    Enum.each(recipient_topics || [], fn list_topic ->
+      Logger.debug("Trying to push message to #{list_topic}\n\n")
+      push_to_socket(topics, list_topic, item)
+    end)
+
     {:noreply, topics}
   end
 
   def handle_cast(%{action: :stream, topic: "user", item: %Notification{} = item}, topics) do
     topic = "user:#{item.user_id}"
-    Enum.each(topics[topic] || [], fn (socket) ->
-      json = %{
-        event: "notification",
-        payload: Pleroma.Web.MastodonAPI.MastodonAPIController.render_notification(socket.assigns["user"], item) |> Poison.encode!
-      } |> Poison.encode!
 
-      send socket.transport_pid, {:text, json}
+    Enum.each(topics[topic] || [], fn socket ->
+      json =
+        %{
+          event: "notification",
+          payload:
+            Pleroma.Web.MastodonAPI.MastodonAPIController.render_notification(
+              socket.assigns["user"],
+              item
+            )
+            |> Jason.encode!()
+        }
+        |> Jason.encode!()
+
+      send(socket.transport_pid, {:text, json})
     end)
+
     {:noreply, topics}
   end
 
   def handle_cast(%{action: :stream, topic: "user", item: item}, topics) do
     Logger.debug("Trying to push to users")
-    recipient_topics = User.get_recipients_from_activity(item)
-    |> Enum.map(fn (%{id: id}) -> "user:#{id}" end)
 
-    Enum.each(recipient_topics, fn (topic) ->
+    recipient_topics =
+      User.get_recipients_from_activity(item)
+      |> Enum.map(fn %{id: id} -> "user:#{id}" end)
+
+    Enum.each(recipient_topics, fn topic ->
       push_to_socket(topics, topic, item)
     end)
+
     {:noreply, topics}
   end
 
@@ -74,7 +143,6 @@ defmodule Pleroma.Web.Streamer do
     sockets_for_topic = Enum.uniq([socket | sockets_for_topic])
     sockets = Map.put(sockets, topic, sockets_for_topic)
     Logger.debug("Got new conn for #{topic}")
-    IO.inspect(sockets)
     {:noreply, sockets}
   end
 
@@ -84,28 +152,77 @@ defmodule Pleroma.Web.Streamer do
     sockets_for_topic = List.delete(sockets_for_topic, socket)
     sockets = Map.put(sockets, topic, sockets_for_topic)
     Logger.debug("Removed conn for #{topic}")
-    IO.inspect(sockets)
     {:noreply, sockets}
   end
 
   def handle_cast(m, state) do
-    IO.inspect("Unknown: #{inspect(m)}, #{inspect(state)}")
+    Logger.info("Unknown: #{inspect(m)}, #{inspect(state)}")
     {:noreply, state}
   end
 
-  def push_to_socket(topics, topic, item) do
-    Enum.each(topics[topic] || [], fn (socket) ->
-      json = %{
-        event: "update",
-        payload: Pleroma.Web.MastodonAPI.StatusView.render("status.json", activity: item, for: socket.assigns[:user]) |> Poison.encode!
-      } |> Poison.encode!
+  defp represent_update(%Activity{} = activity, %User{} = user) do
+    %{
+      event: "update",
+      payload:
+        Pleroma.Web.MastodonAPI.StatusView.render(
+          "status.json",
+          activity: activity,
+          for: user
+        )
+        |> Jason.encode!()
+    }
+    |> Jason.encode!()
+  end
+
+  defp represent_update(%Activity{} = activity) do
+    %{
+      event: "update",
+      payload:
+        Pleroma.Web.MastodonAPI.StatusView.render(
+          "status.json",
+          activity: activity
+        )
+        |> Jason.encode!()
+    }
+    |> Jason.encode!()
+  end
 
-      send socket.transport_pid, {:text, json}
+  def push_to_socket(topics, topic, %Activity{data: %{"type" => "Announce"}} = item) do
+    Enum.each(topics[topic] || [], fn socket ->
+      # Get the current user so we have up-to-date blocks etc.
+      if socket.assigns[:user] do
+        user = User.get_cached_by_ap_id(socket.assigns[:user].ap_id)
+        blocks = user.info.blocks || []
+
+        parent = Object.normalize(item.data["object"])
+
+        unless is_nil(parent) or item.actor in blocks or parent.data["actor"] in blocks do
+          send(socket.transport_pid, {:text, represent_update(item, user)})
+        end
+      else
+        send(socket.transport_pid, {:text, represent_update(item)})
+      end
+    end)
+  end
+
+  def push_to_socket(topics, topic, item) do
+    Enum.each(topics[topic] || [], fn socket ->
+      # Get the current user so we have up-to-date blocks etc.
+      if socket.assigns[:user] do
+        user = User.get_cached_by_ap_id(socket.assigns[:user].ap_id)
+        blocks = user.info.blocks || []
+
+        unless item.actor in blocks do
+          send(socket.transport_pid, {:text, represent_update(item, user)})
+        end
+      else
+        send(socket.transport_pid, {:text, represent_update(item)})
+      end
     end)
   end
 
-  defp internal_topic("user", socket) do
-    "user:#{socket.assigns[:user].id}"
+  defp internal_topic(topic, socket) when topic in ~w[user direct] do
+    "#{topic}:#{socket.assigns[:user].id}"
   end
 
   defp internal_topic(topic, _), do: topic