Procházet zdrojové kódy

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 před 3 roky
rodič
revize
294628d981
15 změnil soubory, kde provedl 191 přidání a 35 odebrání
  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 Zobrazit soubor

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


Načítá se…
Zrušit
Uložit