Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into remake-remodel-dms
authorlain <lain@soykaf.club>
Sat, 30 May 2020 10:31:12 +0000 (12:31 +0200)
committerlain <lain@soykaf.club>
Sat, 30 May 2020 10:31:12 +0000 (12:31 +0200)
1  2 
docs/API/differences_in_mastoapi_responses.md
lib/pleroma/web/activity_pub/builder.ex
lib/pleroma/web/activity_pub/side_effects.ex
lib/pleroma/web/streamer/streamer.ex
test/web/streamer/streamer_test.exs

index a9d1f2f388860a68b5b9c92eaa1579bd4e0d5cd1,434ade9a42f0d8125c1b99232fc9a3a596242d2c..be3c802af23d675d9abb5ec81c55da2d41a1b20f
@@@ -6,10 -6,6 +6,6 @@@ A Pleroma instance can be identified b
  
  Pleroma uses 128-bit ids as opposed to Mastodon's 64 bits. However just like Mastodon's ids they are lexically sortable strings
  
- ## Attachment cap
- Some apps operate under the assumption that no more than 4 attachments can be returned or uploaded. Pleroma however does not enforce any limits on attachment count neither when returning the status object nor when posting.
  ## Timelines
  
  Adding the parameter `with_muted=true` to the timeline queries will also return activities by muted (not by blocked!) users.
@@@ -32,12 -28,20 +28,20 @@@ Has these additional fields under the `
  - `thread_muted`: true if the thread the post belongs to is muted
  - `emoji_reactions`: A list with emoji / reaction maps. The format is `{name: "☕", count: 1, me: true}`. Contains no information about the reacting users, for that use the `/statuses/:id/reactions` endpoint.
  
- ## Attachments
+ ## Media Attachments
  
  Has these additional fields under the `pleroma` object:
  
  - `mime_type`: mime type of the attachment.
  
+ ### Attachment cap
+ Some apps operate under the assumption that no more than 4 attachments can be returned or uploaded. Pleroma however does not enforce any limits on attachment count neither when returning the status object nor when posting.
+ ### Limitations
+ Pleroma does not process remote images and therefore cannot include fields such as `meta` and `blurhash`. It does not support focal points or aspect ratios. The frontend is expected to handle it.
  ## Accounts
  
  The `id` parameter can also be the `nickname` of the user. This only works in these endpoints, not the deeper nested ones for following etc.
@@@ -226,7 -230,3 +230,7 @@@ Has theses additional parameters (whic
  Has these additional fields under the `pleroma` object:
  
  - `unread_count`: contains number unread notifications
 +
 +## Streaming
 +
 +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.
index a40054921055f366975dc80a52e5d209b8a3b94a,51b74414ac6b8404426d2a459932f94dc249fa19..1aac62c69e446d3162020c20c50e6d237210dbe9
@@@ -5,9 -5,9 +5,10 @@@ defmodule Pleroma.Web.ActivityPub.Build
    This module encodes our addressing policies and general shape of our objects.
    """
  
 +  alias Pleroma.Emoji
    alias Pleroma.Object
    alias Pleroma.User
+   alias Pleroma.Web.ActivityPub.Relay
    alias Pleroma.Web.ActivityPub.Utils
    alias Pleroma.Web.ActivityPub.Visibility
  
       }, []}
    end
  
 +  def create(actor, object, recipients) do
 +    {:ok,
 +     %{
 +       "id" => Utils.generate_activity_id(),
 +       "actor" => actor.ap_id,
 +       "to" => recipients,
 +       "object" => object,
 +       "type" => "Create",
 +       "published" => DateTime.utc_now() |> DateTime.to_iso8601()
 +     }, []}
 +  end
 +
 +  def chat_message(actor, recipient, content, opts \\ []) do
 +    basic = %{
 +      "id" => Utils.generate_object_id(),
 +      "actor" => actor.ap_id,
 +      "type" => "ChatMessage",
 +      "to" => [recipient],
 +      "content" => content,
 +      "published" => DateTime.utc_now() |> DateTime.to_iso8601(),
 +      "emoji" => Emoji.Formatter.get_emoji_map(content)
 +    }
 +
 +    case opts[:attachment] do
 +      %Object{data: attachment_data} ->
 +        {
 +          :ok,
 +          Map.put(basic, "attachment", attachment_data),
 +          []
 +        }
 +
 +      _ ->
 +        {:ok, basic, []}
 +    end
 +  end
 +
    @spec tombstone(String.t(), String.t()) :: {:ok, map(), keyword()}
    def tombstone(actor, id) do
      {:ok,
      end
    end
  
+   @spec announce(User.t(), Object.t(), keyword()) :: {:ok, map(), keyword()}
    def announce(actor, object, options \\ []) do
      public? = Keyword.get(options, :public, false)
-     to = [actor.follower_address, object.data["actor"]]
  
      to =
-       if public? do
-         [Pleroma.Constants.as_public() | to]
-       else
-         to
+       cond do
+         actor.ap_id == Relay.relay_ap_id() ->
+           [actor.follower_address]
+         public? ->
+           [actor.follower_address, object.data["actor"], Pleroma.Constants.as_public()]
+         true ->
+           [actor.follower_address, object.data["actor"]]
        end
  
      {:ok,
index 02296b210562679f1dfb7e3c0c7065f67cfde9e3,fb627545041c26f6520958dcd81c401c63e1e714..a34bf6a054143dbfba10152f386607192a8ee6b4
@@@ -6,15 -6,12 +6,15 @@@ defmodule Pleroma.Web.ActivityPub.SideE
    collection, and so on.
    """
    alias Pleroma.Activity
 +  alias Pleroma.Chat
    alias Pleroma.Notification
    alias Pleroma.Object
    alias Pleroma.Repo
    alias Pleroma.User
    alias Pleroma.Web.ActivityPub.ActivityPub
 +  alias Pleroma.Web.ActivityPub.Pipeline
    alias Pleroma.Web.ActivityPub.Utils
 +  alias Pleroma.Web.Streamer
  
    def handle(object, meta \\ [])
  
      {:ok, object, meta}
    end
  
 +  # Tasks this handles
 +  # - Actually create object
 +  # - Rollback if we couldn't create it
 +  # - Set up notifications
 +  def handle(%{data: %{"type" => "Create"}} = activity, meta) do
 +    with {:ok, _object, _meta} <- handle_object_creation(meta[:object_data], meta) do
 +      Notification.create_notifications(activity)
 +      {:ok, activity, meta}
 +    else
 +      e -> Repo.rollback(e)
 +    end
 +  end
 +
    # Tasks this handles:
    # - Add announce to object
    # - Set up notification
    # - Stream out the announce
    def handle(%{data: %{"type" => "Announce"}} = object, meta) do
      announced_object = Object.get_by_ap_id(object.data["object"])
+     user = User.get_cached_by_ap_id(object.data["actor"])
  
      Utils.add_announce_to_object(object, announced_object)
  
-     Notification.create_notifications(object)
-     ActivityPub.stream_out(object)
+     if !User.is_internal_user?(user) do
+       Notification.create_notifications(object)
+       ActivityPub.stream_out(object)
+     end
  
      {:ok, object, meta}
    end
      {:ok, object, meta}
    end
  
 +  def handle_object_creation(%{"type" => "ChatMessage"} = object, meta) do
 +    with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do
 +      actor = User.get_cached_by_ap_id(object.data["actor"])
 +      recipient = User.get_cached_by_ap_id(hd(object.data["to"]))
 +
 +      [[actor, recipient], [recipient, actor]]
 +      |> Enum.each(fn [user, other_user] ->
 +        if user.local do
 +          if user.ap_id == actor.ap_id do
 +            Chat.get_or_create(user.id, other_user.ap_id)
 +          else
 +            Chat.bump_or_create(user.id, other_user.ap_id)
 +          end
 +        end
 +      end)
 +
 +      Streamer.stream(["user", "user:pleroma_chat"], object)
 +      {:ok, object, meta}
 +    end
 +  end
 +
 +  # Nothing to do
 +  def handle_object_creation(object) do
 +    {:ok, object}
 +  end
 +
    def handle_undoing(%{data: %{"type" => "Like"}} = object) do
      with %Object{} = liked_object <- Object.get_by_ap_id(object.data["object"]),
           {:ok, _} <- Utils.remove_like_from_object(object, liked_object),
index 331490a784f8754f5e721516bf311b28f0e07f76,0cf41189b2f7b75e06dad3d1135b46f7a38f9562..2201cbfef87690eaab47557021a47c247ad7bede
@@@ -10,7 -10,6 +10,7 @@@ defmodule Pleroma.Web.Streamer d
    alias Pleroma.Conversation.Participation
    alias Pleroma.Notification
    alias Pleroma.Object
 +  alias Pleroma.Repo
    alias Pleroma.User
    alias Pleroma.Web.ActivityPub.ActivityPub
    alias Pleroma.Web.ActivityPub.Visibility
@@@ -23,7 -22,7 +23,7 @@@
    def registry, do: @registry
  
    @public_streams ["public", "public:local", "public:media", "public:local:media"]
 -  @user_streams ["user", "user:notification", "direct"]
 +  @user_streams ["user", "user:notification", "direct", "user:pleroma_chat"]
  
    @doc "Expands and authorizes a stream, and registers the process for streaming."
    @spec get_topic_and_add_socket(stream :: String.t(), User.t() | nil, Map.t() | nil) ::
           false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, item_host),
           false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, parent_host),
           true <- thread_containment(item, user),
-          false <- CommonAPI.thread_muted?(user, item) do
+          false <- CommonAPI.thread_muted?(user, parent) do
        false
      else
        _ -> true
      end)
    end
  
 +  defp do_stream(topic, %{data: %{"type" => "ChatMessage"}} = object)
 +       when topic in ["user", "user:pleroma_chat"] do
 +    recipients = [object.data["actor"] | object.data["to"]]
 +
 +    topics =
 +      %{ap_id: recipients, local: true}
 +      |> Pleroma.User.Query.build()
 +      |> Repo.all()
 +      |> Enum.map(fn %{id: id} = user -> {user, "#{topic}:#{id}"} end)
 +
 +    Enum.each(topics, fn {user, topic} ->
 +      Registry.dispatch(@registry, topic, fn list ->
 +        Enum.each(list, fn {pid, _auth} ->
 +          text = StreamerView.render("chat_update.json", object, user, recipients)
 +          send(pid, {:text, text})
 +        end)
 +      end)
 +    end)
 +  end
 +
    defp do_stream("user", item) do
      Logger.debug("Trying to push to users")
  
index ffbff35ca32046f6883b20a84b108c929353b27b,3f012259a0a07ad1a5c514c09f70eef05d723fa9..bcb05a02d11fa4b8740171a668ca0caca18f9a53
@@@ -9,11 -9,9 +9,11 @@@ defmodule Pleroma.Web.StreamerTest d
  
    alias Pleroma.Conversation.Participation
    alias Pleroma.List
 +  alias Pleroma.Object
    alias Pleroma.User
    alias Pleroma.Web.CommonAPI
    alias Pleroma.Web.Streamer
 +  alias Pleroma.Web.StreamerView
  
    @moduletag needs_streamer: true, capture_log: true
  
        refute Streamer.filtered_by_user?(user, announce)
      end
  
+     test "it streams boosts of mastodon user in the 'user' stream", %{user: user} do
+       Streamer.get_topic_and_add_socket("user", user)
+       other_user = insert(:user)
+       {:ok, activity} = CommonAPI.post(other_user, %{status: "hey"})
+       data =
+         File.read!("test/fixtures/mastodon-announce.json")
+         |> Poison.decode!()
+         |> Map.put("object", activity.data["object"])
+         |> Map.put("actor", user.ap_id)
+       {:ok, %Pleroma.Activity{data: _data, local: false} = announce} =
+         Pleroma.Web.ActivityPub.Transmogrifier.handle_incoming(data)
+       assert_receive {:render_with_user, Pleroma.Web.StreamerView, "update.json", ^announce}
+       refute Streamer.filtered_by_user?(user, announce)
+     end
      test "it sends notify to in the 'user' stream", %{user: user, notify: notify} do
        Streamer.get_topic_and_add_socket("user", user)
        Streamer.stream("user", notify)
        refute Streamer.filtered_by_user?(user, notify)
      end
  
 +    test "it sends chat messages to the 'user:pleroma_chat' stream", %{user: user} do
 +      other_user = insert(:user)
 +
 +      {:ok, create_activity} = CommonAPI.post_chat_message(other_user, user, "hey")
 +      object = Object.normalize(create_activity, false)
 +      Streamer.get_topic_and_add_socket("user:pleroma_chat", user)
 +      Streamer.stream("user:pleroma_chat", object)
 +      text = StreamerView.render("chat_update.json", object, user, [user.ap_id, other_user.ap_id])
 +      assert_receive {:text, ^text}
 +    end
 +
 +    test "it sends chat messages to the 'user' stream", %{user: user} do
 +      other_user = insert(:user)
 +
 +      {:ok, create_activity} = CommonAPI.post_chat_message(other_user, user, "hey")
 +      object = Object.normalize(create_activity, false)
 +      Streamer.get_topic_and_add_socket("user", user)
 +      Streamer.stream("user", object)
 +      text = StreamerView.render("chat_update.json", object, user, [user.ap_id, other_user.ap_id])
 +      assert_receive {:text, ^text}
 +    end
 +
 +    test "it sends chat message notifications to the 'user:notification' stream", %{user: user} do
 +      other_user = insert(:user)
 +
 +      {:ok, create_activity} = CommonAPI.post_chat_message(other_user, user, "hey")
 +
 +      notify =
 +        Repo.get_by(Pleroma.Notification, user_id: user.id, activity_id: create_activity.id)
 +        |> Repo.preload(:activity)
 +
 +      Streamer.get_topic_and_add_socket("user:notification", user)
 +      Streamer.stream("user:notification", notify)
 +      assert_receive {:render_with_user, _, _, ^notify}
 +      refute Streamer.filtered_by_user?(user, notify)
 +    end
 +
      test "it doesn't send notify to the 'user:notification' stream when a user is blocked", %{
        user: user
      } do