Fix tagpolicy to also work with Update
[akkoma] / lib / pleroma / web / common_api.ex
index b003e30c7b6fd84b56ede42da927f60efa60c585..f1f51acf5c9bcc55e15d08987e11b04c3351f5a9 100644 (file)
@@ -5,7 +5,6 @@
 defmodule Pleroma.Web.CommonAPI do
   alias Pleroma.Activity
   alias Pleroma.Conversation.Participation
-  alias Pleroma.Formatter
   alias Pleroma.Object
   alias Pleroma.ThreadMute
   alias Pleroma.User
@@ -30,57 +29,6 @@ defmodule Pleroma.Web.CommonAPI do
     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,
-              idempotency_key: opts[:idempotency_key]
-            )} do
-      {:ok, activity}
-    else
-      {:common_pipeline, {:reject, _} = e} -> e
-      e -> e
-    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),
@@ -228,17 +176,7 @@ defmodule Pleroma.Web.CommonAPI do
       {:find_object, _} ->
         {:error, :not_found}
 
-      {:common_pipeline,
-       {
-         :error,
-         {
-           :validate_object,
-           {
-             :error,
-             changeset
-           }
-         }
-       }} = e ->
+      {:common_pipeline, {:error, {:validate, {:error, changeset}}}} = e ->
         if {:object, {"already liked by this actor", []}} in changeset.errors do
           {:ok, :already_liked}
         else
@@ -382,6 +320,10 @@ defmodule Pleroma.Web.CommonAPI do
     end
   end
 
+  def get_quoted_visibility(nil), do: nil
+
+  def get_quoted_visibility(activity), do: get_replied_to_visibility(activity)
+
   def check_expiry_date({:ok, nil} = res), do: res
 
   def check_expiry_date({:ok, in_seconds}) do
@@ -399,41 +341,99 @@ defmodule Pleroma.Web.CommonAPI do
     |> check_expiry_date()
   end
 
-  def listen(user, data) do
-    with {:ok, draft} <- ActivityDraft.listen(user, data) do
-      ActivityPub.listen(draft.changes)
-    end
-  end
-
   def post(user, %{status: _} = data) do
     with {:ok, draft} <- ActivityDraft.create(user, data) do
       ActivityPub.create(draft.changes, draft.preview?)
     end
   end
 
-  def pin(id, %{ap_id: user_ap_id} = user) do
-    with %Activity{
-           actor: ^user_ap_id,
-           data: %{"type" => "Create"},
-           object: %Object{data: %{"type" => object_type}}
-         } = activity <- Activity.get_by_id_with_object(id),
-         true <- object_type in ["Note", "Article", "Question"],
-         true <- Visibility.is_public?(activity),
-         {:ok, _user} <- User.add_pinnned_activity(user, activity) do
+  def update(user, orig_activity, changes) do
+    with orig_object <- Object.normalize(orig_activity),
+         {:ok, new_object} <- make_update_data(user, orig_object, changes),
+         {:ok, update_data, _} <- Builder.update(user, new_object),
+         {:ok, update, _} <- Pipeline.common_pipeline(update_data, local: true) do
+      {:ok, update}
+    else
+      _ -> {:error, nil}
+    end
+  end
+
+  defp make_update_data(user, orig_object, changes) do
+    kept_params = %{
+      visibility: Visibility.get_visibility(orig_object),
+      in_reply_to_id:
+        with replied_id when is_binary(replied_id) <- orig_object.data["inReplyTo"],
+             %Activity{id: activity_id} <- Activity.get_create_by_object_ap_id(replied_id) do
+          activity_id
+        else
+          _ -> nil
+        end
+    }
+
+    params = Map.merge(changes, kept_params)
+
+    with {:ok, draft} <- ActivityDraft.create(user, params) do
+      change =
+        Object.Updater.make_update_object_data(orig_object.data, draft.object, Utils.make_date())
+
+      {:ok, change}
+    else
+      _ -> {:error, nil}
+    end
+  end
+
+  @spec pin(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, term()}
+  def pin(id, %User{} = user) do
+    with %Activity{} = activity <- create_activity_by_id(id),
+         true <- activity_belongs_to_actor(activity, user.ap_id),
+         true <- object_type_is_allowed_for_pin(activity.object),
+         true <- activity_is_public(activity),
+         {:ok, pin_data, _} <- Builder.pin(user, activity.object),
+         {:ok, _pin, _} <-
+           Pipeline.common_pipeline(pin_data,
+             local: true,
+             activity_id: id
+           ) do
       {:ok, activity}
     else
-      {:error, %{errors: [pinned_activities: {err, _}]}} -> {:error, err}
-      _ -> {:error, dgettext("errors", "Could not pin")}
+      {:error, {:side_effects, error}} -> error
+      error -> error
+    end
+  end
+
+  defp create_activity_by_id(id) do
+    with nil <- Activity.create_by_id_with_object(id) do
+      {:error, :not_found}
+    end
+  end
+
+  defp activity_belongs_to_actor(%{actor: actor}, actor), do: true
+  defp activity_belongs_to_actor(_, _), do: {:error, :ownership_error}
+
+  defp object_type_is_allowed_for_pin(%{data: %{"type" => type}}) do
+    with false <- type in ["Note", "Article", "Question"] do
+      {:error, :not_allowed}
     end
   end
 
+  defp activity_is_public(activity) do
+    with false <- Visibility.is_public?(activity) do
+      {:error, :visibility_error}
+    end
+  end
+
+  @spec unpin(String.t(), User.t()) :: {:ok, User.t()} | {:error, term()}
   def unpin(id, user) do
-    with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id(id),
-         {:ok, _user} <- User.remove_pinnned_activity(user, activity) do
+    with %Activity{} = activity <- create_activity_by_id(id),
+         {:ok, unpin_data, _} <- Builder.unpin(user, activity.object),
+         {:ok, _unpin, _} <-
+           Pipeline.common_pipeline(unpin_data,
+             local: true,
+             activity_id: activity.id,
+             expires_at: activity.data["expires_at"],
+             featured_address: user.featured_address
+           ) do
       {:ok, activity}
-    else
-      {:error, %{errors: [pinned_activities: {err, _}]}} -> {:error, err}
-      _ -> {:error, dgettext("errors", "Could not unpin")}
     end
   end
 
@@ -468,9 +468,7 @@ defmodule Pleroma.Web.CommonAPI do
     else
       {what, result} = error ->
         Logger.warn(
-          "CommonAPI.remove_mute/2 failed. #{what}: #{result}, user_id: #{user_id}, activity_id: #{
-            activity_id
-          }"
+          "CommonAPI.remove_mute/2 failed. #{what}: #{result}, user_id: #{user_id}, activity_id: #{activity_id}"
         )
 
         {:error, error}