Merge develop
[akkoma] / lib / pleroma / web / common_api / common_api.ex
index d452429c01185ab92602a5a48ceaee2801748a9e..b49a9579167e75c1f584b7a3e7b447f8cf02fdd2 100644 (file)
@@ -4,10 +4,8 @@
 
 defmodule Pleroma.Web.CommonAPI do
   alias Pleroma.Activity
-  alias Pleroma.ActivityExpiration
   alias Pleroma.Conversation.Participation
-  alias Pleroma.FollowingRelationship
-  alias Pleroma.Notification
+  alias Pleroma.Formatter
   alias Pleroma.Object
   alias Pleroma.ThreadMute
   alias Pleroma.User
@@ -24,6 +22,60 @@ defmodule Pleroma.Web.CommonAPI do
   require Pleroma.Constants
   require Logger
 
+  def block(blocker, blocked) do
+    with {:ok, block_data, _} <- Builder.block(blocker, blocked),
+         {:ok, block, _} <- Pipeline.common_pipeline(block_data, local: true) do
+      {:ok, block}
+    end
+  end
+
+  def post_chat_message(%User{} = user, %User{} = recipient, content, opts \\ []) do
+    with maybe_attachment <- opts[:media_id] && Object.get_by_id(opts[:media_id]),
+         :ok <- validate_chat_content_length(content, !!maybe_attachment),
+         {_, {:ok, chat_message_data, _meta}} <-
+           {:build_object,
+            Builder.chat_message(
+              user,
+              recipient.ap_id,
+              content |> format_chat_content,
+              attachment: maybe_attachment
+            )},
+         {_, {:ok, create_activity_data, _meta}} <-
+           {:build_create_activity, Builder.create(user, chat_message_data, [recipient.ap_id])},
+         {_, {:ok, %Activity{} = activity, _meta}} <-
+           {:common_pipeline,
+            Pipeline.common_pipeline(create_activity_data,
+              local: true
+            )} do
+      {:ok, activity}
+    end
+  end
+
+  defp format_chat_content(nil), do: nil
+
+  defp format_chat_content(content) do
+    {text, _, _} =
+      content
+      |> Formatter.html_escape("text/plain")
+      |> Formatter.linkify()
+      |> (fn {text, mentions, tags} ->
+            {String.replace(text, ~r/\r?\n/, "<br>"), mentions, tags}
+          end).()
+
+    text
+  end
+
+  defp validate_chat_content_length(_, true), do: :ok
+  defp validate_chat_content_length(nil, false), do: {:error, :no_content}
+
+  defp validate_chat_content_length(content, _) do
+    if String.length(content) <= Pleroma.Config.get([:instance, :chat_limit]) do
+      :ok
+    else
+      {:error, :content_too_long}
+    end
+  end
+
   def unblock(blocker, blocked) do
     with {_, %Activity{} = block} <- {:fetch_block, Utils.fetch_latest_block(blocker, blocked)},
          {:ok, unblock_data, _} <- Builder.undo(blocker, block),
@@ -46,10 +98,14 @@ defmodule Pleroma.Web.CommonAPI do
   def follow(follower, followed) do
     timeout = Pleroma.Config.get([:activitypub, :follow_handshake_timeout])
 
-    with {:ok, follower} <- User.maybe_direct_follow(follower, followed),
-         {:ok, activity} <- ActivityPub.follow(follower, followed),
+    with {:ok, follow_data, _} <- Builder.follow(follower, followed),
+         {:ok, activity, _} <- Pipeline.common_pipeline(follow_data, local: true),
          {:ok, follower, followed} <- User.wait_and_refresh(timeout, follower, followed) do
-      {:ok, follower, followed, activity}
+      if activity.data["state"] == "reject" do
+        {:error, :rejected}
+      else
+        {:ok, follower, followed, activity}
+      end
     end
   end
 
@@ -63,32 +119,16 @@ defmodule Pleroma.Web.CommonAPI do
 
   def accept_follow_request(follower, followed) do
     with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
-         {:ok, follower} <- User.follow(follower, followed),
-         {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"),
-         {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_accept),
-         {:ok, _activity} <-
-           ActivityPub.accept(%{
-             to: [follower.ap_id],
-             actor: followed,
-             object: follow_activity.data["id"],
-             type: "Accept"
-           }) do
+         {:ok, accept_data, _} <- Builder.accept(followed, follow_activity),
+         {:ok, _activity, _} <- Pipeline.common_pipeline(accept_data, local: true) do
       {:ok, follower}
     end
   end
 
   def reject_follow_request(follower, followed) do
     with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
-         {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject"),
-         {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_reject),
-         {:ok, _notifications} <- Notification.dismiss(follow_activity),
-         {:ok, _activity} <-
-           ActivityPub.reject(%{
-             to: [follower.ap_id],
-             actor: followed,
-             object: follow_activity.data["id"],
-             type: "Reject"
-           }) do
+         {:ok, reject_data, _} <- Builder.reject(followed, follow_activity),
+         {:ok, _activity, _} <- Pipeline.common_pipeline(reject_data, local: true) do
       {:ok, follower}
     end
   end
@@ -248,18 +288,19 @@ defmodule Pleroma.Web.CommonAPI do
          {:ok, options, choices} <- normalize_and_validate_choices(choices, object) do
       answer_activities =
         Enum.map(choices, fn index ->
-          answer_data = make_answer_data(user, object, Enum.at(options, index)["name"])
-
-          {:ok, activity} =
-            ActivityPub.create(%{
-              to: answer_data["to"],
-              actor: user,
-              context: object.data["context"],
-              object: answer_data,
-              additional: %{"cc" => answer_data["cc"]}
-            })
-
-          activity
+          {:ok, answer_object, _meta} =
+            Builder.answer(user, object, Enum.at(options, index)["name"])
+
+          {:ok, activity_data, _meta} = Builder.create(user, answer_object, [])
+
+          {:ok, activity, _meta} =
+            activity_data
+            |> Map.put("cc", answer_object["cc"])
+            |> Map.put("context", answer_object["context"])
+            |> Pipeline.common_pipeline(local: true)
+
+          # TODO: Do preload of Pleroma.Object in Pipeline
+          Activity.normalize(activity.data)
         end)
 
       object = Object.get_cached_by_ap_id(object.data["id"])
@@ -280,8 +321,13 @@ defmodule Pleroma.Web.CommonAPI do
     end
   end
 
-  defp get_options_and_max_count(%{data: %{"anyOf" => any_of}}), do: {any_of, Enum.count(any_of)}
-  defp get_options_and_max_count(%{data: %{"oneOf" => one_of}}), do: {one_of, 1}
+  defp get_options_and_max_count(%{data: %{"anyOf" => any_of}})
+       when is_list(any_of) and any_of != [],
+       do: {any_of, Enum.count(any_of)}
+
+  defp get_options_and_max_count(%{data: %{"oneOf" => one_of}})
+       when is_list(one_of) and one_of != [],
+       do: {one_of, 1}
 
   defp normalize_and_validate_choices(choices, object) do
     choices = Enum.map(choices, fn i -> if is_binary(i), do: String.to_integer(i), else: i end)
@@ -334,9 +380,9 @@ defmodule Pleroma.Web.CommonAPI do
   def check_expiry_date({:ok, nil} = res), do: res
 
   def check_expiry_date({:ok, in_seconds}) do
-    expiry = NaiveDateTime.utc_now() |> NaiveDateTime.add(in_seconds)
+    expiry = DateTime.add(DateTime.utc_now(), in_seconds)
 
-    if ActivityExpiration.expires_late_enough?(expiry) do
+    if Pleroma.Workers.PurgeExpiredActivity.expires_late_enough?(expiry) do
       {:ok, expiry}
     else
       {:error, "Expiry date is too soon"}
@@ -404,8 +450,19 @@ defmodule Pleroma.Web.CommonAPI do
     end
   end
 
-  def add_mute(user, activity) do
-    with {:ok, _} <- ThreadMute.add_mute(user.id, activity.data["context"]) do
+  def add_mute(user, activity, params \\ %{}) do
+    expires_in = Map.get(params, :expires_in, 0)
+
+    with {:ok, _} <- ThreadMute.add_mute(user.id, activity.data["context"]),
+         _ <- Pleroma.Notification.mark_context_as_read(user, activity.data["context"]) do
+      if expires_in > 0 do
+        Pleroma.Workers.MuteExpireWorker.enqueue(
+          "unmute_conversation",
+          %{"user_id" => user.id, "activity_id" => activity.id},
+          schedule_in: expires_in
+        )
+      end
+
       {:ok, activity}
     else
       {:error, _} -> {:error, dgettext("errors", "conversation is already muted")}
@@ -417,12 +474,13 @@ defmodule Pleroma.Web.CommonAPI do
     {:ok, activity}
   end
 
-  def thread_muted?(%{id: nil} = _user, _activity), do: false
-
-  def thread_muted?(user, activity) do
-    ThreadMute.exists?(user.id, activity.data["context"])
+  def thread_muted?(%User{id: user_id}, %{data: %{"context" => context}})
+      when is_binary(context) do
+    ThreadMute.exists?(user_id, context)
   end
 
+  def thread_muted?(_, _), do: false
+
   def report(user, data) do
     with {:ok, account} <- get_reported_account(data.account_id),
          {:ok, {content_html, _, _}} <- make_report_content_html(data[:comment]),