commit: 36be0d32a477b0a8dac279c8114b35a5117775d1
parent 4960e040c106266892a100c9cfa732f07c134700
Author: lain <lain@soykaf.club>
Date: Thu, 29 May 2025 08:25:21 +0000
Merge branch 'expiring-blocks' into 'develop'
Add expiring blocks
See merge request pleroma/pleroma!4351
Diffstat:
12 files changed, 113 insertions(+), 33 deletions(-)
diff --git a/changelog.d/expiring-blocks.add b/changelog.d/expiring-blocks.add
@@ -0,0 +1 @@
+Add `duration` to the block endpoint, which makes block expire
+\ No newline at end of file
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
@@ -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
diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex
@@ -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()}
diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex
@@ -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}
diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -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"],
diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex
@@ -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},
diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex
@@ -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
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -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
diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -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)
diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex
@@ -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
diff --git a/lib/pleroma/workers/mute_expire_worker.ex b/lib/pleroma/workers/mute_expire_worker.ex
@@ -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
diff --git a/test/pleroma/web/common_api_test.exs b/test/pleroma/web/common_api_test.exs
@@ -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