Ingestion Pipeline: Undo-Follow aka Unfollow
This commit is contained in:
parent
173e977e28
commit
bd977a3c3f
@ -983,7 +983,7 @@ defmodule Pleroma.User do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
|
def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
|
||||||
{:error, "Not subscribed!"}
|
{:error, "Can't unfollow yourself!"}
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec unfollow(User.t(), User.t()) :: {:ok, User.t(), Activity.t()} | {:error, String.t()}
|
@spec unfollow(User.t(), User.t()) :: {:ok, User.t(), Activity.t()} | {:error, String.t()}
|
||||||
@ -1771,17 +1771,11 @@ defmodule Pleroma.User do
|
|||||||
# Remove all relationships
|
# Remove all relationships
|
||||||
user
|
user
|
||||||
|> get_followers()
|
|> get_followers()
|
||||||
|> Enum.each(fn follower ->
|
|> Enum.each(fn follower -> ActivityPub.unfollow(follower, user) end)
|
||||||
ActivityPub.unfollow(follower, user)
|
|
||||||
unfollow(follower, user)
|
|
||||||
end)
|
|
||||||
|
|
||||||
user
|
user
|
||||||
|> get_friends()
|
|> get_friends()
|
||||||
|> Enum.each(fn followed ->
|
|> Enum.each(fn followed -> ActivityPub.unfollow(user, followed) end)
|
||||||
ActivityPub.unfollow(user, followed)
|
|
||||||
unfollow(user, followed)
|
|
||||||
end)
|
|
||||||
|
|
||||||
delete_user_activities(user)
|
delete_user_activities(user)
|
||||||
delete_notifications_from_user_activities(user)
|
delete_notifications_from_user_activities(user)
|
||||||
|
@ -20,8 +20,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.Upload
|
alias Pleroma.Upload
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.ActivityPub.Builder
|
||||||
alias Pleroma.Web.ActivityPub.MRF
|
alias Pleroma.Web.ActivityPub.MRF
|
||||||
|
alias Pleroma.Web.ActivityPub.Pipeline
|
||||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
alias Pleroma.Web.Streamer
|
alias Pleroma.Web.Streamer
|
||||||
alias Pleroma.Web.WebFinger
|
alias Pleroma.Web.WebFinger
|
||||||
alias Pleroma.Workers.BackgroundWorker
|
alias Pleroma.Workers.BackgroundWorker
|
||||||
@ -322,22 +325,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec unfollow(User.t(), User.t(), String.t() | nil, boolean()) ::
|
@spec unfollow(User.t(), User.t()) :: {:ok, Activity.t()} | nil | {:error, any()}
|
||||||
{:ok, Activity.t()} | nil | {:error, any()}
|
def unfollow(follower, followed) do
|
||||||
def unfollow(follower, followed, activity_id \\ nil, local \\ true) do
|
with {:ok, result} <- Repo.transaction(fn -> do_unfollow(follower, followed) end) do
|
||||||
with {:ok, result} <-
|
|
||||||
Repo.transaction(fn -> do_unfollow(follower, followed, activity_id, local) end) do
|
|
||||||
result
|
result
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp do_unfollow(follower, followed, activity_id, local) do
|
defp do_unfollow(follower, followed) do
|
||||||
with %Activity{} = follow_activity <- fetch_latest_follow(follower, followed),
|
with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
|
||||||
{:ok, follow_activity} <- update_follow_state(follow_activity, "cancelled"),
|
{:ok, unfollow_data, _meta} <- Builder.undo(follower, follow_activity),
|
||||||
unfollow_data <- make_unfollow_data(follower, followed, follow_activity, activity_id),
|
{:ok, activity, _meta} <- Pipeline.common_pipeline(unfollow_data, local: true) do
|
||||||
{:ok, activity} <- insert(unfollow_data, local),
|
|
||||||
_ <- notify_and_stream(activity),
|
|
||||||
:ok <- maybe_federate(activity) do
|
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
else
|
else
|
||||||
nil -> nil
|
nil -> nil
|
||||||
|
@ -254,10 +254,12 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
|||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle(%{data: %{"type" => "Undo", "object" => undone_object}} = object, meta) do
|
def handle(%{data: %{"type" => "Undo", "object" => undone_activity}} = object, meta) do
|
||||||
with undone_object <- Activity.get_by_ap_id(undone_object),
|
with undone_activity <- Activity.get_by_ap_id(undone_activity),
|
||||||
:ok <- handle_undoing(undone_object) do
|
:ok <- handle_undoing(undone_activity) do
|
||||||
{:ok, object, meta}
|
{:ok, object, meta}
|
||||||
|
else
|
||||||
|
e -> {:error, e}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -456,35 +458,48 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_undoing(%{data: %{"type" => "Like"}} = object) do
|
def handle_undoing(%{data: %{"type" => "Like"}} = activity) do
|
||||||
object.data["object"]
|
activity.data["object"]
|
||||||
|> Object.get_by_ap_id()
|
|> Object.get_by_ap_id()
|
||||||
|> undo_like(object)
|
|> undo_like(activity)
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_undoing(%{data: %{"type" => "EmojiReact"}} = object) do
|
def handle_undoing(%{data: %{"type" => "EmojiReact"}} = activity) do
|
||||||
with %Object{} = reacted_object <- Object.get_by_ap_id(object.data["object"]),
|
with %Object{} = reacted_object <- Object.get_by_ap_id(activity.data["object"]),
|
||||||
{:ok, _} <- Utils.remove_emoji_reaction_from_object(object, reacted_object),
|
{:ok, _} <- Utils.remove_emoji_reaction_from_object(activity, reacted_object),
|
||||||
{:ok, _} <- Repo.delete(object) do
|
{:ok, _} <- Repo.delete(activity) do
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_undoing(%{data: %{"type" => "Announce"}} = object) do
|
def handle_undoing(%{data: %{"type" => "Announce"}} = activity) do
|
||||||
with %Object{} = liked_object <- Object.get_by_ap_id(object.data["object"]),
|
with %Object{} = liked_object <- Object.get_by_ap_id(activity.data["object"]),
|
||||||
{:ok, _} <- Utils.remove_announce_from_object(object, liked_object),
|
{:ok, _} <- Utils.remove_announce_from_object(activity, liked_object),
|
||||||
{:ok, _} <- Repo.delete(object) do
|
{:ok, _} <- Repo.delete(activity) do
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_undoing(
|
def handle_undoing(
|
||||||
%{data: %{"type" => "Block", "actor" => blocker, "object" => blocked}} = object
|
%{data: %{"type" => "Block", "actor" => blocker, "object" => blocked}} = activity
|
||||||
) do
|
) do
|
||||||
with %User{} = blocker <- User.get_cached_by_ap_id(blocker),
|
with %User{} = blocker <- User.get_cached_by_ap_id(blocker),
|
||||||
%User{} = blocked <- User.get_cached_by_ap_id(blocked),
|
%User{} = blocked <- User.get_cached_by_ap_id(blocked),
|
||||||
{:ok, _} <- User.unblock(blocker, 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
|
:ok
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -516,52 +516,30 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def handle_incoming(
|
def handle_incoming(
|
||||||
%{
|
%{"type" => "Undo", "object" => %{"type" => objtype, "id" => object_id}} = data,
|
||||||
"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,
|
|
||||||
_options
|
_options
|
||||||
)
|
)
|
||||||
when type in ["Like", "EmojiReact", "Announce", "Block"] do
|
when objtype in ~w[Like EmojiReact Announce Block Follow] do
|
||||||
with {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) 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}
|
{:ok, activity}
|
||||||
|
else
|
||||||
|
{:error, _} = e -> e
|
||||||
|
e -> {:error, e}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# For Undos that don't have the complete object attached, try to find it in our database.
|
# For Undos that don't have the complete object attached, try to find it in our database.
|
||||||
def handle_incoming(
|
def handle_incoming(%{"type" => "Undo", "object" => object} = activity, options)
|
||||||
%{
|
|
||||||
"type" => "Undo",
|
|
||||||
"object" => object
|
|
||||||
} = activity,
|
|
||||||
options
|
|
||||||
)
|
|
||||||
when is_binary(object) do
|
when is_binary(object) do
|
||||||
with %Activity{data: data} <- Activity.get_by_ap_id(object) do
|
with %Activity{data: data} <- Activity.get_by_ap_id(object) do
|
||||||
activity
|
activity
|
||||||
|> Map.put("object", data)
|
|> Map.put("object", data)
|
||||||
|> handle_incoming(options)
|
|> handle_incoming(options)
|
||||||
else
|
else
|
||||||
_e -> :error
|
{:error, _} = e -> e
|
||||||
|
e -> {:error, e}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -572,25 +572,6 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||||||
|> Maps.put_if_present("id", activity_id)
|
|> Maps.put_if_present("id", activity_id)
|
||||||
end
|
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()) ::
|
@spec add_announce_to_object(Activity.t(), Object.t()) ::
|
||||||
{:ok, Object.t()} | {:error, Ecto.Changeset.t()}
|
{:ok, Object.t()} | {:error, Ecto.Changeset.t()}
|
||||||
def add_announce_to_object(
|
def add_announce_to_object(
|
||||||
@ -624,18 +605,6 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||||||
|
|
||||||
defp take_announcements(_), 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
|
#### Block-related helpers
|
||||||
@spec fetch_latest_block(User.t(), User.t()) :: Activity.t() | nil
|
@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
|
def fetch_latest_block(%User{ap_id: blocker_id}, %User{ap_id: blocked_id}) do
|
||||||
|
@ -32,7 +32,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.UndoHandlingTest do
|
|||||||
assert activity.data["type"] == "Undo"
|
assert activity.data["type"] == "Undo"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it returns an error for incoming unlikes wihout a like activity" do
|
test "it returns an error for incoming unlikes without a like activity" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
{:ok, activity} = CommonAPI.post(user, %{status: "leave a like pls"})
|
{:ok, activity} = CommonAPI.post(user, %{status: "leave a like pls"})
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.UndoHandlingTest do
|
|||||||
|> Jason.decode!()
|
|> Jason.decode!()
|
||||||
|> Map.put("object", activity.data["object"])
|
|> Map.put("object", activity.data["object"])
|
||||||
|
|
||||||
assert Transmogrifier.handle_incoming(data) == :error
|
assert Transmogrifier.handle_incoming(data) == {:error, nil}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it works for incoming unlikes with an existing like activity" do
|
test "it works for incoming unlikes with an existing like activity" do
|
||||||
|
@ -15,6 +15,7 @@ defmodule Pleroma.Web.CommonAPITest do
|
|||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
alias Pleroma.Web.ActivityPub.Visibility
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
alias Pleroma.Web.AdminAPI.AccountView
|
alias Pleroma.Web.AdminAPI.AccountView
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
@ -1213,15 +1214,15 @@ defmodule Pleroma.Web.CommonAPITest do
|
|||||||
assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
|
assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
|
||||||
assert User.get_follow_state(follower, followed) == nil
|
assert User.get_follow_state(follower, followed) == nil
|
||||||
|
|
||||||
assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
|
assert %{id: ^activity_id, data: %{"state" => "reject"}} =
|
||||||
Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
|
Utils.fetch_latest_follow(follower, followed)
|
||||||
|
|
||||||
assert %{
|
assert %{
|
||||||
data: %{
|
data: %{
|
||||||
"type" => "Undo",
|
"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
|
||||||
|
|
||||||
test "cancels a pending follow for a remote user" do
|
test "cancels a pending follow for a remote user" do
|
||||||
@ -1235,15 +1236,15 @@ defmodule Pleroma.Web.CommonAPITest do
|
|||||||
assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
|
assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
|
||||||
assert User.get_follow_state(follower, followed) == nil
|
assert User.get_follow_state(follower, followed) == nil
|
||||||
|
|
||||||
assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
|
assert %{id: ^activity_id, data: %{"state" => "reject"}} =
|
||||||
Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
|
Utils.fetch_latest_follow(follower, followed)
|
||||||
|
|
||||||
assert %{
|
assert %{
|
||||||
data: %{
|
data: %{
|
||||||
"type" => "Undo",
|
"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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user