Просмотр исходного кода

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 лет назад
Родитель
Сommit
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 =


Загрузка…
Отмена
Сохранить