Merge branch 'expiring-blocks' into 'develop'

Add expiring blocks

See merge request pleroma/pleroma!4351
This commit is contained in:
vaartis 2025-04-15 09:57:04 +00:00
commit 9aa8f80d59
12 changed files with 112 additions and 33 deletions

View file

@ -0,0 +1 @@
Add `duration` to the block endpoint, which makes block expire

View file

@ -1708,7 +1708,9 @@ defmodule Pleroma.User do
end
end
def block(%User{} = blocker, %User{} = blocked) do
def block(blocker, blocked, params \\ %{})
def block(%User{} = blocker, %User{} = blocked, params) do
# sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
blocker =
if following?(blocker, blocked) do
@ -1738,12 +1740,33 @@ defmodule Pleroma.User do
{:ok, blocker} = update_follower_count(blocker)
{:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
add_to_block(blocker, blocked)
duration = Map.get(params, :duration, 0)
expires_at =
if duration > 0 do
DateTime.utc_now()
|> DateTime.add(duration)
else
nil
end
user_block = add_to_block(blocker, blocked, expires_at)
if duration > 0 do
Pleroma.Workers.MuteExpireWorker.new(
%{"op" => "unblock_user", "blocker_id" => blocker.id, "blocked_id" => blocked.id},
scheduled_at: expires_at
)
|> Oban.insert()
end
user_block
end
# helper to handle the block given only an actor's AP id
def block(%User{} = blocker, %{ap_id: ap_id}) do
block(blocker, get_cached_by_ap_id(ap_id))
def block(%User{} = blocker, %{ap_id: ap_id}, params) do
block(blocker, get_cached_by_ap_id(ap_id), params)
end
def unblock(%User{} = blocker, %User{} = blocked) do
@ -2779,10 +2802,10 @@ defmodule Pleroma.User do
set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
end
@spec add_to_block(User.t(), User.t()) ::
@spec add_to_block(User.t(), User.t(), integer() | nil) ::
{:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
defp add_to_block(%User{} = user, %User{} = blocked) do
with {:ok, relationship} <- UserRelationship.create_block(user, blocked) do
defp add_to_block(%User{} = user, %User{} = blocked, expires_at) do
with {:ok, relationship} <- UserRelationship.create_block(user, blocked, expires_at) do
@cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
{:ok, relationship}
end

View file

@ -327,8 +327,8 @@ defmodule Pleroma.Web.ActivityPub.Builder do
}, []}
end
@spec block(User.t(), User.t()) :: {:ok, map(), keyword()}
def block(blocker, blocked) do
@spec block(User.t(), User.t(), map()) :: {:ok, map(), keyword()}
def block(blocker, blocked, params) do
{:ok,
%{
"id" => Utils.generate_activity_id(),
@ -336,7 +336,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do
"actor" => blocker.ap_id,
"object" => blocked.ap_id,
"to" => [blocked.ap_id]
}, []}
}, Keyword.new(params)}
end
@spec announce(User.t(), Object.t(), keyword()) :: {:ok, map(), keyword()}

View file

@ -145,7 +145,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
) do
with %User{} = blocker <- User.get_cached_by_ap_id(blocking_user),
%User{} = blocked <- User.get_cached_by_ap_id(blocked_user) do
User.block(blocker, blocked)
User.block(blocker, blocked, Enum.into(meta, %{}))
end
{:ok, object, meta}

View file

@ -284,18 +284,6 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
:query,
%Schema{allOf: [BooleanLike], default: true},
"Mute notifications in addition to statuses? Defaults to `true`."
),
Operation.parameter(
:duration,
:query,
%Schema{type: :integer},
"Expire the mute in `duration` seconds. Default 0 for infinity"
),
Operation.parameter(
:expires_in,
:query,
%Schema{type: :integer, default: 0},
"Deprecated, use `duration` instead"
)
],
responses: %{
@ -323,16 +311,37 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
tags: ["Account actions"],
summary: "Block",
operationId: "AccountController.block",
requestBody: request_body("Parameters", block_request()),
security: [%{"oAuth" => ["follow", "write:blocks"]}],
description:
"Block the given account. Clients should filter statuses from this account if received (e.g. due to a boost in the Home timeline)",
parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
parameters: [
%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}
],
responses: %{
200 => Operation.response("Relationship", "application/json", AccountRelationship)
}
}
end
defp block_request do
%Schema{
title: "AccountBlockRequest",
description: "POST body for blocking an account",
type: :object,
properties: %{
duration: %Schema{
type: :integer,
nullable: true,
description: "Expire the mute in `duration` seconds. Default 0 for infinity"
}
},
example: %{
"duration" => 86_400
}
}
end
def unblock_operation do
%Operation{
tags: ["Account actions"],

View file

@ -34,6 +34,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
id: FlakeID,
locked: %Schema{type: :boolean},
mute_expires_at: %Schema{type: :string, format: "date-time", nullable: true},
block_expires_at: %Schema{type: :string, format: "date-time", nullable: true},
note: %Schema{type: :string, format: :html},
statuses_count: %Schema{type: :integer},
url: %Schema{type: :string, format: :uri},

View file

@ -27,9 +27,9 @@ defmodule Pleroma.Web.CommonAPI do
require Logger
@spec block(User.t(), User.t()) :: {:ok, Activity.t()} | Pipeline.errors()
def block(blocked, blocker) do
with {:ok, block_data, _} <- Builder.block(blocker, blocked),
{:ok, block, _} <- Pipeline.common_pipeline(block_data, local: true) do
def block(blocked, blocker, params \\ %{}) do
with {:ok, block_data, meta} <- Builder.block(blocker, blocked, params),
{:ok, block, _} <- Pipeline.common_pipeline(block_data, meta ++ [local: true]) do
{:ok, block}
end
end

View file

@ -501,8 +501,14 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
end
@doc "POST /api/v1/accounts/:id/block"
def block(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do
with {:ok, _activity} <- CommonAPI.block(blocked, blocker) do
def block(
%{
assigns: %{user: blocker, account: blocked},
private: %{open_api_spex: %{body_params: params}}
} = conn,
_params
) do
with {:ok, _activity} <- CommonAPI.block(blocked, blocker, params) do
render(conn, "relationship.json", user: blocker, target: blocked)
else
{:error, message} -> json_response(conn, :forbidden, %{error: message})
@ -607,7 +613,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
users: users,
for: user,
as: :user,
embed_relationships: embed_relationships?(params)
embed_relationships: embed_relationships?(params),
blocks: true
)
end

View file

@ -340,6 +340,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|> maybe_put_unread_notification_count(user, opts[:for])
|> maybe_put_email_address(user, opts[:for])
|> maybe_put_mute_expires_at(user, opts[:for], opts)
|> maybe_put_block_expires_at(user, opts[:for], opts)
|> maybe_show_birthday(user, opts[:for])
end
@ -476,6 +477,16 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
defp maybe_put_mute_expires_at(data, _, _, _), do: data
defp maybe_put_block_expires_at(data, %User{} = user, target, %{blocks: true}) do
Map.put(
data,
:block_expires_at,
UserRelationship.get_block_expire_date(target, user)
)
end
defp maybe_put_block_expires_at(data, _, _, _), do: data
defp maybe_show_birthday(data, %User{id: user_id} = user, %User{id: user_id}) do
data
|> Kernel.put_in([:pleroma, :birthday], user.birthday)

View file

@ -157,7 +157,8 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
"pleroma:bookmark_folders",
if Pleroma.Language.LanguageDetector.configured?() do
"pleroma:language_detection"
end
end,
"pleroma:block_expiration"
]
|> Enum.filter(& &1)
end

View file

@ -5,9 +5,13 @@
defmodule Pleroma.Workers.MuteExpireWorker do
use Oban.Worker, queue: :background
alias Pleroma.User
@impl true
def perform(%Job{args: %{"op" => "unmute_user", "muter_id" => muter_id, "mutee_id" => mutee_id}}) do
Pleroma.User.unmute(muter_id, mutee_id)
def perform(%Job{
args: %{"op" => "unmute_user", "muter_id" => muter_id, "mutee_id" => mutee_id}
}) do
User.unmute(muter_id, mutee_id)
:ok
end
@ -18,6 +22,17 @@ defmodule Pleroma.Workers.MuteExpireWorker do
:ok
end
def perform(%Job{
args: %{"op" => "unblock_user", "blocker_id" => blocker_id, "blocked_id" => blocked_id}
}) do
Pleroma.Web.CommonAPI.unblock(
User.get_cached_by_id(blocked_id),
User.get_cached_by_id(blocker_id)
)
:ok
end
@impl true
def timeout(_job), do: :timer.seconds(5)
end

View file

@ -111,6 +111,17 @@ defmodule Pleroma.Web.CommonAPITest do
end
end
test "add expiring block", %{blocker: blocker, blocked: blocked} do
{:ok, _} = CommonAPI.block(blocked, blocker, %{expires_in: 60})
assert User.blocks?(blocker, blocked)
worker = Pleroma.Workers.MuteExpireWorker
args = %{"op" => "unblock_user", "blocker_id" => blocker.id, "blocked_id" => blocked.id}
assert :ok = perform_job(worker, args)
refute User.blocks?(blocker, blocked)
end
test "it blocks and does not federate if outgoing blocks are disabled", %{
blocker: blocker,
blocked: blocked