Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into feature/expire...
authorlain <lain@soykaf.club>
Wed, 4 Nov 2020 15:51:42 +0000 (16:51 +0100)
committerlain <lain@soykaf.club>
Wed, 4 Nov 2020 15:51:42 +0000 (16:51 +0100)
14 files changed:
CHANGELOG.md
config/config.exs
lib/pleroma/user.ex
lib/pleroma/web/api_spec/operations/account_operation.ex
lib/pleroma/web/api_spec/operations/status_operation.ex
lib/pleroma/web/common_api.ex
lib/pleroma/web/mastodon_api/controllers/account_controller.ex
lib/pleroma/web/mastodon_api/controllers/status_controller.ex
lib/pleroma/workers/mute_expire_worker.ex [new file with mode: 0644]
test/pleroma/notification_test.exs
test/pleroma/user_test.exs
test/pleroma/web/common_api_test.exs
test/pleroma/web/mastodon_api/controllers/notification_controller_test.exs
test/pleroma/web/mastodon_api/views/account_view_test.exs

index 3a82f7d6c59d2dd858b86a14cb8c9bb765788836..f0b90ff40c31a425df336b1bb906aa24043b4050 100644 (file)
@@ -43,6 +43,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Admin API: (`GET /api/pleroma/admin/users`) added filters user by `actor_type`
 - Pleroma API: Add `idempotency_key` to the chat message entity that can be used for optimistic message sending.
 - Pleroma API: (`GET /api/v1/pleroma/federation_status`) Add a way to get a list of unreachable instances.
+- Mastodon API: User and conversation mutes can now auto-expire if `expires_in` parameter was given while adding the mute.
 
 </details>
 
index c0b6ac1d66fe03ab2ba6c180f217d22949846d23..c77529e8707e9e7afe3c19a11ce588dea75fac16 100644 (file)
@@ -562,7 +562,8 @@ config :pleroma, Oban,
     background: 5,
     remote_fetcher: 2,
     attachments_cleanup: 5,
-    new_users_digest: 1
+    new_users_digest: 1,
+    mute_expire: 5
   ],
   plugins: [Oban.Plugins.Pruner],
   crontab: [
index 059d94e30f2361f3152e6502c66892f3644ad4e5..8e4ec8064a2052655776826ff2b40936363afdf1 100644 (file)
@@ -1324,14 +1324,48 @@ defmodule Pleroma.User do
     |> Repo.all()
   end
 
-  @spec mute(User.t(), User.t(), boolean()) ::
+  @spec mute(User.t(), User.t(), map()) ::
           {:ok, list(UserRelationship.t())} | {:error, String.t()}
-  def mute(%User{} = muter, %User{} = mutee, notifications? \\ true) do
-    add_to_mutes(muter, mutee, notifications?)
+  def mute(%User{} = muter, %User{} = mutee, params \\ %{}) do
+    notifications? = Map.get(params, :notifications, true)
+    expires_in = Map.get(params, :expires_in, 0)
+
+    with {:ok, user_mute} <- UserRelationship.create_mute(muter, mutee),
+         {:ok, user_notification_mute} <-
+           (notifications? && UserRelationship.create_notification_mute(muter, mutee)) ||
+             {:ok, nil} do
+      if expires_in > 0 do
+        Pleroma.Workers.MuteExpireWorker.enqueue(
+          "unmute_user",
+          %{"muter_id" => muter.id, "mutee_id" => mutee.id},
+          schedule_in: expires_in
+        )
+      end
+
+      {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
+    end
   end
 
   def unmute(%User{} = muter, %User{} = mutee) do
-    remove_from_mutes(muter, mutee)
+    with {:ok, user_mute} <- UserRelationship.delete_mute(muter, mutee),
+         {:ok, user_notification_mute} <-
+           UserRelationship.delete_notification_mute(muter, mutee) do
+      {:ok, [user_mute, user_notification_mute]}
+    end
+  end
+
+  def unmute(muter_id, mutee_id) do
+    with {:muter, %User{} = muter} <- {:muter, User.get_by_id(muter_id)},
+         {:mutee, %User{} = mutee} <- {:mutee, User.get_by_id(mutee_id)} do
+      unmute(muter, mutee)
+    else
+      {who, result} = error ->
+        Logger.warn(
+          "User.unmute/2 failed. #{who}: #{result}, muter_id: #{muter_id}, mutee_id: #{mutee_id}"
+        )
+
+        {:error, error}
+    end
   end
 
   def subscribe(%User{} = subscriber, %User{} = target) do
@@ -2320,23 +2354,6 @@ defmodule Pleroma.User do
     UserRelationship.delete_block(user, blocked)
   end
 
-  defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
-    with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
-         {:ok, user_notification_mute} <-
-           (notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
-             {:ok, nil} do
-      {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
-    end
-  end
-
-  defp remove_from_mutes(user, %User{} = muted_user) do
-    with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
-         {:ok, user_notification_mute} <-
-           UserRelationship.delete_notification_mute(user, muted_user) do
-      {:ok, [user_mute, user_notification_mute]}
-    end
-  end
-
   def set_invisible(user, invisible) do
     params = %{invisible: invisible}
 
index 4934b77885da13e5c2b673eb677ca1e616912c16..451aa2477c450852e52cdf7c802f267c84e5f6c8 100644 (file)
@@ -262,6 +262,12 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
           :query,
           %Schema{allOf: [BooleanLike], default: true},
           "Mute notifications in addition to statuses? Defaults to `true`."
+        ),
+        Operation.parameter(
+          :expires_in,
+          :query,
+          %Schema{type: :integer, default: 0},
+          "Expire the mute in `expires_in` seconds. Default 0 for infinity"
         )
       ],
       responses: %{
@@ -723,10 +729,17 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
           nullable: true,
           description: "Mute notifications in addition to statuses? Defaults to true.",
           default: true
+        },
+        expires_in: %Schema{
+          type: :integer,
+          nullable: true,
+          description: "Expire the mute in `expires_in` seconds. Default 0 for infinity",
+          default: 0
         }
       },
       example: %{
-        "notifications" => true
+        "notifications" => true,
+        "expires_in" => 86_400
       }
     }
   end
index d7ebde6f6f5c0050e73f8f4c5c3e5433c3e1b345..b3b6ceb68940784e518a0a2197e1f2022146d83c 100644 (file)
@@ -223,7 +223,27 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
       security: [%{"oAuth" => ["write:mutes"]}],
       description: "Do not receive notifications for the thread that this status is part of.",
       operationId: "StatusController.mute_conversation",
-      parameters: [id_param()],
+      requestBody:
+        request_body("Parameters", %Schema{
+          type: :object,
+          properties: %{
+            expires_in: %Schema{
+              type: :integer,
+              nullable: true,
+              description: "Expire the mute in `expires_in` seconds. Default 0 for infinity",
+              default: 0
+            }
+          }
+        }),
+      parameters: [
+        id_param(),
+        Operation.parameter(
+          :expires_in,
+          :query,
+          %Schema{type: :integer, default: 0},
+          "Expire the mute in `expires_in` seconds. Default 0 for infinity"
+        )
+      ],
       responses: %{
         200 => status_response(),
         400 => Operation.response("Error", "application/json", ApiError)
index 318ffc5d0077f7bff258fb2f95e161b7ffdd8660..0ab1b115d55270881a3e3f889e039e40434133dc 100644 (file)
@@ -454,20 +454,46 @@ defmodule Pleroma.Web.CommonAPI do
     end
   end
 
-  def add_mute(user, activity) 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")}
     end
   end
 
-  def remove_mute(user, activity) do
+  def remove_mute(%User{} = user, %Activity{} = activity) do
     ThreadMute.remove_mute(user.id, activity.data["context"])
     {:ok, activity}
   end
 
+  def remove_mute(user_id, activity_id) do
+    with {:user, %User{} = user} <- {:user, User.get_by_id(user_id)},
+         {:activity, %Activity{} = activity} <- {:activity, Activity.get_by_id(activity_id)} do
+      remove_mute(user, activity)
+    else
+      {what, result} = error ->
+        Logger.warn(
+          "CommonAPI.remove_mute/2 failed. #{what}: #{result}, user_id: #{user_id}, activity_id: #{
+            activity_id
+          }"
+        )
+
+        {:error, error}
+    end
+  end
+
   def thread_muted?(%User{id: user_id}, %{data: %{"context" => context}})
       when is_binary(context) do
     ThreadMute.exists?(user_id, context)
index a2715cf28a3d35dc3363714c35f0f029d45523c8..784fdc9755f832830d9e97ea257ad23661ea6388 100644 (file)
@@ -394,7 +394,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
 
   @doc "POST /api/v1/accounts/:id/mute"
   def mute(%{assigns: %{user: muter, account: muted}, body_params: params} = conn, _params) do
-    with {:ok, _user_relationships} <- User.mute(muter, muted, params.notifications) do
+    with {:ok, _user_relationships} <- User.mute(muter, muted, params) do
       render(conn, "relationship.json", user: muter, target: muted)
     else
       {:error, message} -> json_response(conn, :forbidden, %{error: message})
index 6848adace711aab8ee9b496d2eea58c4e2604181..4d9be5240c912fc03416501af38035c4261e0c46 100644 (file)
@@ -284,9 +284,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
   end
 
   @doc "POST /api/v1/statuses/:id/mute"
-  def mute_conversation(%{assigns: %{user: user}} = conn, %{id: id}) do
+  def mute_conversation(%{assigns: %{user: user}, body_params: params} = conn, %{id: id}) do
     with %Activity{} = activity <- Activity.get_by_id(id),
-         {:ok, activity} <- CommonAPI.add_mute(user, activity) do
+         {:ok, activity} <- CommonAPI.add_mute(user, activity, params) do
       try_render(conn, "show.json", activity: activity, for: user, as: :activity)
     end
   end
diff --git a/lib/pleroma/workers/mute_expire_worker.ex b/lib/pleroma/workers/mute_expire_worker.ex
new file mode 100644 (file)
index 0000000..32a12ba
--- /dev/null
@@ -0,0 +1,20 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Workers.MuteExpireWorker do
+  use Pleroma.Workers.WorkerHelper, queue: "mute_expire"
+
+  @impl Oban.Worker
+  def perform(%Job{args: %{"op" => "unmute_user", "muter_id" => muter_id, "mutee_id" => mutee_id}}) do
+    Pleroma.User.unmute(muter_id, mutee_id)
+    :ok
+  end
+
+  def perform(%Job{
+        args: %{"op" => "unmute_conversation", "user_id" => user_id, "activity_id" => activity_id}
+      }) do
+    Pleroma.Web.CommonAPI.remove_mute(user_id, activity_id)
+    :ok
+  end
+end
index a74fb7bc2cc86812792ff5ba7360775a52d0a7a8..92c0bc8b69fc69371e234b3ed84d7b38a3308100 100644 (file)
@@ -229,7 +229,7 @@ defmodule Pleroma.NotificationTest do
       muter = insert(:user)
       muted = insert(:user)
 
-      {:ok, _user_relationships} = User.mute(muter, muted, false)
+      {:ok, _user_relationships} = User.mute(muter, muted, %{notifications: false})
 
       {:ok, activity} = CommonAPI.post(muted, %{status: "Hi @#{muter.nickname}"})
 
@@ -1015,7 +1015,7 @@ defmodule Pleroma.NotificationTest do
 
     test "it returns notifications for muted user without notifications", %{user: user} do
       muted = insert(:user)
-      {:ok, _user_relationships} = User.mute(user, muted, false)
+      {:ok, _user_relationships} = User.mute(user, muted, %{notifications: false})
 
       {:ok, _activity} = CommonAPI.post(muted, %{status: "hey @#{user.nickname}"})
 
index 9ae52d594e0d961a7afc39d0c1834730a2b861b9..c678dadb3075ba0e092156beee902a4037702c66 100644 (file)
@@ -1008,6 +1008,27 @@ defmodule Pleroma.UserTest do
       assert User.muted_notifications?(user, muted_user)
     end
 
+    test "expiring" do
+      user = insert(:user)
+      muted_user = insert(:user)
+
+      {:ok, _user_relationships} = User.mute(user, muted_user, %{expires_in: 60})
+      assert User.mutes?(user, muted_user)
+
+      worker = Pleroma.Workers.MuteExpireWorker
+      args = %{"op" => "unmute_user", "muter_id" => user.id, "mutee_id" => muted_user.id}
+
+      assert_enqueued(
+        worker: worker,
+        args: args
+      )
+
+      assert :ok = perform_job(worker, args)
+
+      refute User.mutes?(user, muted_user)
+      refute User.muted_notifications?(user, muted_user)
+    end
+
     test "it unmutes users" do
       user = insert(:user)
       muted_user = insert(:user)
@@ -1019,6 +1040,17 @@ defmodule Pleroma.UserTest do
       refute User.muted_notifications?(user, muted_user)
     end
 
+    test "it unmutes users by id" do
+      user = insert(:user)
+      muted_user = insert(:user)
+
+      {:ok, _user_relationships} = User.mute(user, muted_user)
+      {:ok, _user_mute} = User.unmute(user.id, muted_user.id)
+
+      refute User.mutes?(user, muted_user)
+      refute User.muted_notifications?(user, muted_user)
+    end
+
     test "it mutes user without notifications" do
       user = insert(:user)
       muted_user = insert(:user)
@@ -1026,7 +1058,7 @@ defmodule Pleroma.UserTest do
       refute User.mutes?(user, muted_user)
       refute User.muted_notifications?(user, muted_user)
 
-      {:ok, _user_relationships} = User.mute(user, muted_user, false)
+      {:ok, _user_relationships} = User.mute(user, muted_user, %{notifications: false})
 
       assert User.mutes?(user, muted_user)
       refute User.muted_notifications?(user, muted_user)
index c5b90ad84297b7d569eac29dd92069a6f9125328..8e87e69fe0a3b4f84ab70a52d3eb95950d03e5d7 100644 (file)
@@ -3,8 +3,8 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Web.CommonAPITest do
-  use Pleroma.DataCase
   use Oban.Testing, repo: Pleroma.Repo
+  use Pleroma.DataCase
 
   alias Pleroma.Activity
   alias Pleroma.Chat
@@ -922,12 +922,34 @@ defmodule Pleroma.Web.CommonAPITest do
       assert CommonAPI.thread_muted?(user, activity)
     end
 
+    test "add expiring mute", %{user: user, activity: activity} do
+      {:ok, _} = CommonAPI.add_mute(user, activity, %{expires_in: 60})
+      assert CommonAPI.thread_muted?(user, activity)
+
+      worker = Pleroma.Workers.MuteExpireWorker
+      args = %{"op" => "unmute_conversation", "user_id" => user.id, "activity_id" => activity.id}
+
+      assert_enqueued(
+        worker: worker,
+        args: args
+      )
+
+      assert :ok = perform_job(worker, args)
+      refute CommonAPI.thread_muted?(user, activity)
+    end
+
     test "remove mute", %{user: user, activity: activity} do
       CommonAPI.add_mute(user, activity)
       {:ok, _} = CommonAPI.remove_mute(user, activity)
       refute CommonAPI.thread_muted?(user, activity)
     end
 
+    test "remove mute by ids", %{user: user, activity: activity} do
+      CommonAPI.add_mute(user, activity)
+      {:ok, _} = CommonAPI.remove_mute(user.id, activity.id)
+      refute CommonAPI.thread_muted?(user, activity)
+    end
+
     test "check that mutes can't be duplicate", %{user: user, activity: activity} do
       CommonAPI.add_mute(user, activity)
       {:error, _} = CommonAPI.add_mute(user, activity)
index 70ef0e8b5012aa9a15f7fe1fab7fc39015f325f7..5fd518c602c6e1e8ae626175238b4c20eb21589f 100644 (file)
@@ -502,7 +502,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do
 
     assert length(json_response_and_validate_schema(ret_conn, 200)) == 1
 
-    {:ok, _user_relationships} = User.mute(user, user2, false)
+    {:ok, _user_relationships} = User.mute(user, user2, %{notifications: false})
 
     conn = get(conn, "/api/v1/notifications")
 
index 203e61c716a74fa38512cca633c32219f3d70e94..139e32362104907bcb49b38a651c33d7ac3c1281 100644 (file)
@@ -277,7 +277,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
       {:ok, user} = User.follow(user, other_user)
       {:ok, other_user} = User.follow(other_user, user)
       {:ok, _subscription} = User.subscribe(user, other_user)
-      {:ok, _user_relationships} = User.mute(user, other_user, true)
+      {:ok, _user_relationships} = User.mute(user, other_user, %{notifications: true})
       {:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, other_user)
 
       expected =