Compare commits

...

6 commits

Author SHA1 Message Date
Haelwenn b6d44ce578 Merge branch 'features/ingestion-unfollow' into 'develop'
Draft: Ingestion Pipeline: Undo-Follow aka Unfollow

See merge request pleroma/pleroma!3486
2024-04-25 15:47:17 +00:00
lain 50af909c01 Merge branch 'pleroma-card-image-description' into 'develop'
Include image description in status media cards

See merge request pleroma/pleroma!4101
2024-04-19 07:39:05 +00:00
marcin mikołajczak 6f6bede900 Include image description in status media cards
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2024-04-19 10:20:31 +04:00
lain 87b8ac3ce6 Merge branch 'receiverworker-error-handling' into 'develop'
ReceiverWorker: Make sure non-{:ok, _} is returned as {:error, …}

See merge request pleroma/pleroma!4100
2024-04-19 06:04:44 +00:00
Haelwenn (lanodan) Monnier a299ddb10e
ReceiverWorker: Make sure non-{:ok, _} is returned as {:error, …}
Otherwise an error like `{:signature, {:error, {:error, :not_found}}}` ends up considered a success.
2024-04-17 07:43:47 +02:00
Haelwenn (lanodan) Monnier bd977a3c3f
Ingestion Pipeline: Undo-Follow aka Unfollow 2021-07-21 09:19:28 +02:00
14 changed files with 78 additions and 112 deletions

View file

@ -0,0 +1 @@
Include image description in status media cards

View file

@ -0,0 +1 @@
ReceiverWorker: Make sure non-{:ok, _} is returned as {:error, …}

View file

@ -1105,7 +1105,7 @@ defmodule Pleroma.User do
end
def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
{:error, "Not subscribed!"}
{:error, "Can't unfollow yourself!"}
end
@spec unfollow(User.t(), User.t()) :: {:ok, User.t(), Activity.t()} | {:error, String.t()}
@ -1975,17 +1975,11 @@ defmodule Pleroma.User do
# Remove all relationships
user
|> get_followers()
|> Enum.each(fn follower ->
ActivityPub.unfollow(follower, user)
unfollow(follower, user)
end)
|> Enum.each(fn follower -> ActivityPub.unfollow(follower, user) end)
user
|> get_friends()
|> Enum.each(fn followed ->
ActivityPub.unfollow(user, followed)
unfollow(user, followed)
end)
|> Enum.each(fn followed -> ActivityPub.unfollow(user, followed) end)
delete_user_activities(user)
delete_notifications_from_user_activities(user)

View file

@ -20,8 +20,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
alias Pleroma.Repo
alias Pleroma.Upload
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Builder
alias Pleroma.Web.ActivityPub.MRF
alias Pleroma.Web.ActivityPub.Pipeline
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.Streamer
alias Pleroma.Web.WebFinger
alias Pleroma.Workers.BackgroundWorker
@ -359,22 +362,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
end
@spec unfollow(User.t(), User.t(), String.t() | nil, boolean()) ::
{:ok, Activity.t()} | nil | {:error, any()}
def unfollow(follower, followed, activity_id \\ nil, local \\ true) do
with {:ok, result} <-
Repo.transaction(fn -> do_unfollow(follower, followed, activity_id, local) end) do
@spec unfollow(User.t(), User.t()) :: {:ok, Activity.t()} | nil | {:error, any()}
def unfollow(follower, followed) do
with {:ok, result} <- Repo.transaction(fn -> do_unfollow(follower, followed) end) do
result
end
end
defp do_unfollow(follower, followed, activity_id, local) do
with %Activity{} = follow_activity <- fetch_latest_follow(follower, followed),
{:ok, follow_activity} <- update_follow_state(follow_activity, "cancelled"),
unfollow_data <- make_unfollow_data(follower, followed, follow_activity, activity_id),
{:ok, activity} <- insert(unfollow_data, local),
_ <- notify_and_stream(activity),
:ok <- maybe_federate(activity) do
defp do_unfollow(follower, followed) do
with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
{:ok, unfollow_data, _meta} <- Builder.undo(follower, follow_activity),
{:ok, activity, _meta} <- Pipeline.common_pipeline(unfollow_data, local: true) do
{:ok, activity}
else
nil -> nil

View file

@ -268,10 +268,12 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
end
@impl true
def handle(%{data: %{"type" => "Undo", "object" => undone_object}} = object, meta) do
with undone_object <- Activity.get_by_ap_id(undone_object),
:ok <- handle_undoing(undone_object) do
def handle(%{data: %{"type" => "Undo", "object" => undone_activity}} = object, meta) do
with undone_activity <- Activity.get_by_ap_id(undone_activity),
:ok <- handle_undoing(undone_activity) do
{:ok, object, meta}
else
e -> {:error, e}
end
end
@ -545,35 +547,48 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
end
end
def handle_undoing(%{data: %{"type" => "Like"}} = object) do
object.data["object"]
def handle_undoing(%{data: %{"type" => "Like"}} = activity) do
activity.data["object"]
|> Object.get_by_ap_id()
|> undo_like(object)
|> undo_like(activity)
end
def handle_undoing(%{data: %{"type" => "EmojiReact"}} = object) do
with %Object{} = reacted_object <- Object.get_by_ap_id(object.data["object"]),
{:ok, _} <- Utils.remove_emoji_reaction_from_object(object, reacted_object),
{:ok, _} <- Repo.delete(object) do
def handle_undoing(%{data: %{"type" => "EmojiReact"}} = activity) do
with %Object{} = reacted_object <- Object.get_by_ap_id(activity.data["object"]),
{:ok, _} <- Utils.remove_emoji_reaction_from_object(activity, reacted_object),
{:ok, _} <- Repo.delete(activity) do
:ok
end
end
def handle_undoing(%{data: %{"type" => "Announce"}} = object) do
with %Object{} = liked_object <- Object.get_by_ap_id(object.data["object"]),
{:ok, _} <- Utils.remove_announce_from_object(object, liked_object),
{:ok, _} <- Repo.delete(object) do
def handle_undoing(%{data: %{"type" => "Announce"}} = activity) do
with %Object{} = liked_object <- Object.get_by_ap_id(activity.data["object"]),
{:ok, _} <- Utils.remove_announce_from_object(activity, liked_object),
{:ok, _} <- Repo.delete(activity) do
:ok
end
end
def handle_undoing(
%{data: %{"type" => "Block", "actor" => blocker, "object" => blocked}} = object
%{data: %{"type" => "Block", "actor" => blocker, "object" => blocked}} = activity
) do
with %User{} = blocker <- User.get_cached_by_ap_id(blocker),
%User{} = blocked <- User.get_cached_by_ap_id(blocked),
{:ok, _} <- User.unblock(blocker, blocked),
{:ok, _} <- Repo.delete(object) do
{:ok, _} <- Repo.delete(activity) do
:ok
end
end
def handle_undoing(
%{data: %{"type" => "Follow", "object" => followed, "actor" => follower}} = activity
) do
with %User{} = follower <- User.get_cached_by_ap_id(follower),
%User{} = followed <- User.get_cached_by_ap_id(followed),
{:ok, _activity} <- Utils.update_follow_state_for_all(activity, "reject"),
{:ok, nil} <- FollowingRelationship.update(follower, followed, :follow_reject) do
Notification.dismiss(activity)
:ok
end
end

View file

@ -534,52 +534,30 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
def handle_incoming(
%{
"type" => "Undo",
"object" => %{"type" => "Follow", "object" => followed},
"actor" => follower,
"id" => id
} = _data,
_options
) do
with %User{local: true} = followed <- User.get_cached_by_ap_id(followed),
{:ok, %User{} = follower} <- User.get_or_fetch_by_ap_id(follower),
{:ok, activity} <- ActivityPub.unfollow(follower, followed, id, false) do
User.unfollow(follower, followed)
{:ok, activity}
else
_e -> :error
end
end
def handle_incoming(
%{
"type" => "Undo",
"object" => %{"type" => type}
} = data,
%{"type" => "Undo", "object" => %{"type" => objtype, "id" => object_id}} = data,
_options
)
when type in ["Like", "EmojiReact", "Announce", "Block"] do
with {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
when objtype in ~w[Like EmojiReact Announce Block Follow] do
with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
{_, %Activity{}} <- {:exists, Activity.get_by_ap_id(object_id)},
{:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
{:ok, activity}
else
{:error, _} = e -> e
e -> {:error, e}
end
end
# For Undos that don't have the complete object attached, try to find it in our database.
def handle_incoming(
%{
"type" => "Undo",
"object" => object
} = activity,
options
)
def handle_incoming(%{"type" => "Undo", "object" => object} = activity, options)
when is_binary(object) do
with %Activity{data: data} <- Activity.get_by_ap_id(object) do
activity
|> Map.put("object", data)
|> handle_incoming(options)
else
_e -> :error
{:error, _} = e -> e
e -> {:error, e}
end
end

View file

@ -601,25 +601,6 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|> Maps.put_if_present("id", activity_id)
end
def make_undo_data(
%User{ap_id: actor, follower_address: follower_address},
%Activity{
data: %{"id" => undone_activity_id, "context" => context},
actor: undone_activity_actor
},
activity_id \\ nil
) do
%{
"type" => "Undo",
"actor" => actor,
"object" => undone_activity_id,
"to" => [follower_address, undone_activity_actor],
"cc" => [Pleroma.Constants.as_public()],
"context" => context
}
|> Maps.put_if_present("id", activity_id)
end
@spec add_announce_to_object(Activity.t(), Object.t()) ::
{:ok, Object.t()} | {:error, Ecto.Changeset.t()}
def add_announce_to_object(
@ -653,18 +634,6 @@ defmodule Pleroma.Web.ActivityPub.Utils do
defp take_announcements(_), do: []
#### Unfollow-related helpers
def make_unfollow_data(follower, followed, follow_activity, activity_id) do
%{
"type" => "Undo",
"actor" => follower.ap_id,
"to" => [followed.ap_id],
"object" => follow_activity.data
}
|> Maps.put_if_present("id", activity_id)
end
#### Block-related helpers
@spec fetch_latest_block(User.t(), User.t()) :: Activity.t() | nil
def fetch_latest_block(%User{ap_id: blocker_id}, %User{ap_id: blocked_id}) do

View file

@ -58,6 +58,10 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
format: :uri,
description: "Preview thumbnail"
},
image_description: %Schema{
type: :string,
description: "Alternate text that describes what is in the thumbnail"
},
title: %Schema{type: :string, description: "Title of linked resource"},
description: %Schema{type: :string, description: "Description of preview"}
}

View file

@ -589,6 +589,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
provider_url: page_url_data.scheme <> "://" <> page_url_data.host,
url: page_url,
image: image_url,
image_description: rich_media["image:alt"] || "",
title: rich_media["title"] || "",
description: rich_media["description"] || "",
pleroma: %{

View file

@ -52,7 +52,8 @@ defmodule Pleroma.Workers.ReceiverWorker do
{:error, {:reject, reason}} -> {:cancel, reason}
{:signature, false} -> {:cancel, :invalid_signature}
{:error, {:error, reason = "Object has been deleted"}} -> {:cancel, reason}
e -> e
{:error, _} = e -> e
e -> {:error, e}
end
end
end

View file

@ -41,7 +41,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.UndoHandlingTest do
|> Jason.decode!()
|> Map.put("object", activity.data["object"])
assert Transmogrifier.handle_incoming(data) == :error
assert Transmogrifier.handle_incoming(data) == {:error, nil}
end
test "it works for incoming unlikes with an existing like activity" do

View file

@ -16,6 +16,7 @@ defmodule Pleroma.Web.CommonAPITest do
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.AdminAPI.AccountView
alias Pleroma.Web.CommonAPI
@ -1433,15 +1434,15 @@ defmodule Pleroma.Web.CommonAPITest do
assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
assert User.get_follow_state(follower, followed) == nil
assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
assert %{id: ^activity_id, data: %{"state" => "reject"}} =
Utils.fetch_latest_follow(follower, followed)
assert %{
data: %{
"type" => "Undo",
"object" => %{"type" => "Follow", "state" => "cancelled"}
"object" => %{"type" => "Follow", "state" => "reject"}
}
} = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
} = Utils.fetch_latest_undo(follower)
end
test "cancels a pending follow for a remote user" do
@ -1455,15 +1456,15 @@ defmodule Pleroma.Web.CommonAPITest do
assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
assert User.get_follow_state(follower, followed) == nil
assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
assert %{id: ^activity_id, data: %{"state" => "reject"}} =
Utils.fetch_latest_follow(follower, followed)
assert %{
data: %{
"type" => "Undo",
"object" => %{"type" => "Follow", "state" => "cancelled"}
"object" => %{"type" => "Follow", "state" => "reject"}
}
} = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
} = Utils.fetch_latest_undo(follower)
end
end

View file

@ -1717,6 +1717,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
card_data = %{
"image" => "http://ia.media-imdb.com/images/rock.jpg",
"image_description" => "",
"provider_name" => "example.com",
"provider_url" => "https://example.com",
"title" => "The Rock",
@ -1770,6 +1771,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
"title" => "Pleroma",
"description" => "",
"image" => nil,
"image_description" => "",
"provider_name" => "example.com",
"provider_url" => "https://example.com",
"url" => "https://example.com/ogp-missing-data",

View file

@ -773,6 +773,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
page_url = "http://example.com"
card = %{
"image:alt" => "Example image description",
url: page_url,
site_name: "Example site name",
title: "Example website",
@ -780,7 +781,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
description: "Example description"
}
%{provider_name: "example.com"} =
%{provider_name: "example.com", image_description: "Example image description"} =
StatusView.render("card.json", %{page_url: page_url, rich_media: card})
end