瀏覽代碼

Merge branch 'feature/expire-mutes' into 'develop'

Expiring mutes for users and activities

Closes #1817

See merge request pleroma/pleroma!2971
2298-weird-follow-issue
lain 3 年之前
父節點
當前提交
294628d981
共有 15 個檔案被更改,包括 191 行新增35 行删除
  1. +1
    -0
      CHANGELOG.md
  2. +2
    -1
      config/config.exs
  3. +4
    -0
      docs/API/differences_in_mastoapi_responses.md
  4. +38
    -21
      lib/pleroma/user.ex
  5. +14
    -1
      lib/pleroma/web/api_spec/operations/account_operation.ex
  6. +21
    -1
      lib/pleroma/web/api_spec/operations/status_operation.ex
  7. +28
    -2
      lib/pleroma/web/common_api.ex
  8. +1
    -1
      lib/pleroma/web/mastodon_api/controllers/account_controller.ex
  9. +2
    -2
      lib/pleroma/web/mastodon_api/controllers/status_controller.ex
  10. +20
    -0
      lib/pleroma/workers/mute_expire_worker.ex
  11. +2
    -2
      test/pleroma/notification_test.exs
  12. +33
    -1
      test/pleroma/user_test.exs
  13. +23
    -1
      test/pleroma/web/common_api_test.exs
  14. +1
    -1
      test/pleroma/web/mastodon_api/controllers/notification_controller_test.exs
  15. +1
    -1
      test/pleroma/web/mastodon_api/views/account_view_test.exs

+ 1
- 0
CHANGELOG.md 查看文件

@@ -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>



+ 2
- 1
config/config.exs 查看文件

@@ -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: [


+ 4
- 0
docs/API/differences_in_mastoapi_responses.md 查看文件

@@ -255,6 +255,10 @@ There is an additional `user:pleroma_chat` stream. Incoming chat messages will m

For viewing remote server timelines, there are `public:remote` and `public:remote:media` streams. Each of these accept a parameter like `?instance=lain.com`.

## User muting and thread muting

Both user muting and thread muting can be done for only a certain time by adding an `expires_in` parameter to the API calls and giving the expiration time in seconds.

## Not implemented

Pleroma is generally compatible with the Mastodon 2.7.2 API, but some newer features and non-essential features are omitted. These features usually return an HTTP 200 status code, but with an empty response. While they may be added in the future, they are considered low priority.


+ 38
- 21
lib/pleroma/user.ex 查看文件

@@ -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}



+ 14
- 1
lib/pleroma/web/api_spec/operations/account_operation.ex 查看文件

@@ -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


+ 21
- 1
lib/pleroma/web/api_spec/operations/status_operation.ex 查看文件

@@ -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)


+ 28
- 2
lib/pleroma/web/common_api.ex 查看文件

@@ -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)


+ 1
- 1
lib/pleroma/web/mastodon_api/controllers/account_controller.ex 查看文件

@@ -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})


+ 2
- 2
lib/pleroma/web/mastodon_api/controllers/status_controller.ex 查看文件

@@ -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


+ 20
- 0
lib/pleroma/workers/mute_expire_worker.ex 查看文件

@@ -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

+ 2
- 2
test/pleroma/notification_test.exs 查看文件

@@ -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}"})



+ 33
- 1
test/pleroma/user_test.exs 查看文件

@@ -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)


+ 23
- 1
test/pleroma/web/common_api_test.exs 查看文件

@@ -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)


+ 1
- 1
test/pleroma/web/mastodon_api/controllers/notification_controller_test.exs 查看文件

@@ -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")



+ 1
- 1
test/pleroma/web/mastodon_api/views/account_view_test.exs 查看文件

@@ -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 =


Loading…
取消
儲存