Merge branch 'feature/undo-like-federation' into 'develop'
Support undo like over ActivityPub (Fix #139) Closes #139 See merge request pleroma/pleroma!161
This commit is contained in:
commit
40af452594
|
@ -160,7 +160,7 @@ def add_links({subs, text}) do
|
||||||
links =
|
links =
|
||||||
Regex.scan(@link_regex, text)
|
Regex.scan(@link_regex, text)
|
||||||
|> Enum.map(fn [url] -> {Ecto.UUID.generate(), url} end)
|
|> Enum.map(fn [url] -> {Ecto.UUID.generate(), url} end)
|
||||||
|> Enum.sort_by(fn ({_, url}) -> -String.length(url) end)
|
|> Enum.sort_by(fn {_, url} -> -String.length(url) end)
|
||||||
|
|
||||||
uuid_text =
|
uuid_text =
|
||||||
links
|
links
|
||||||
|
|
|
@ -131,11 +131,19 @@ def like(
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def unlike(%User{} = actor, %Object{} = object) do
|
def unlike(
|
||||||
with %Activity{} = activity <- get_existing_like(actor.ap_id, object),
|
%User{} = actor,
|
||||||
{:ok, _activity} <- Repo.delete(activity),
|
%Object{} = object,
|
||||||
{:ok, object} <- remove_like_from_object(activity, object) do
|
activity_id \\ nil,
|
||||||
{:ok, object}
|
local \\ true
|
||||||
|
) do
|
||||||
|
with %Activity{} = like_activity <- get_existing_like(actor.ap_id, object),
|
||||||
|
unlike_data <- make_unlike_data(actor, like_activity, activity_id),
|
||||||
|
{:ok, unlike_activity} <- insert(unlike_data, local),
|
||||||
|
{:ok, _activity} <- Repo.delete(like_activity),
|
||||||
|
{:ok, object} <- remove_like_from_object(like_activity, object),
|
||||||
|
:ok <- maybe_federate(unlike_activity) do
|
||||||
|
{:ok, unlike_activity, like_activity, object}
|
||||||
else
|
else
|
||||||
_e -> {:ok, object}
|
_e -> {:ok, object}
|
||||||
end
|
end
|
||||||
|
|
|
@ -241,6 +241,24 @@ def handle_incoming(
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_incoming(
|
||||||
|
%{
|
||||||
|
"type" => "Undo",
|
||||||
|
"object" => %{"type" => "Like", "object" => object_id},
|
||||||
|
"actor" => actor,
|
||||||
|
"id" => id
|
||||||
|
} = data
|
||||||
|
) do
|
||||||
|
with %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
|
||||||
|
{:ok, object} <-
|
||||||
|
get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id),
|
||||||
|
{:ok, activity, _, _} <- ActivityPub.unlike(actor, object, id, false) do
|
||||||
|
{:ok, activity}
|
||||||
|
else
|
||||||
|
e -> :error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# TODO
|
# TODO
|
||||||
# Accept
|
# Accept
|
||||||
# Undo for non-Announce
|
# Undo for non-Announce
|
||||||
|
|
|
@ -315,6 +315,23 @@ def make_unannounce_data(
|
||||||
if activity_id, do: Map.put(data, "id", activity_id), else: data
|
if activity_id, do: Map.put(data, "id", activity_id), else: data
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def make_unlike_data(
|
||||||
|
%User{ap_id: ap_id} = user,
|
||||||
|
%Activity{data: %{"context" => context}} = activity,
|
||||||
|
activity_id
|
||||||
|
) do
|
||||||
|
data = %{
|
||||||
|
"type" => "Undo",
|
||||||
|
"actor" => ap_id,
|
||||||
|
"object" => activity.data,
|
||||||
|
"to" => [user.follower_address, activity.data["actor"]],
|
||||||
|
"cc" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
|
"context" => context
|
||||||
|
}
|
||||||
|
|
||||||
|
if activity_id, do: Map.put(data, "id", activity_id), else: data
|
||||||
|
end
|
||||||
|
|
||||||
def add_announce_to_object(%Activity{data: %{"actor" => actor}}, object) do
|
def add_announce_to_object(%Activity{data: %{"actor" => actor}}, object) do
|
||||||
with announcements <- [actor | object.data["announcements"] || []] |> Enum.uniq() do
|
with announcements <- [actor | object.data["announcements"] || []] |> Enum.uniq() do
|
||||||
update_element_in_object("announcement", announcements, object)
|
update_element_in_object("announcement", announcements, object)
|
||||||
|
|
|
@ -323,7 +323,7 @@ def fav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def unfav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
def unfav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
||||||
with {:ok, %{data: %{"id" => id}}} = CommonAPI.unfavorite(ap_id_or_id, user),
|
with {:ok, _, _, %{data: %{"id" => id}}} = CommonAPI.unfavorite(ap_id_or_id, user),
|
||||||
%Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do
|
%Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do
|
||||||
render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity})
|
render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity})
|
||||||
end
|
end
|
||||||
|
|
|
@ -82,14 +82,14 @@ defp unrepeat(%User{} = user, ap_id_or_id) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def fav(%User{} = user, ap_id_or_id) do
|
def fav(%User{} = user, ap_id_or_id) do
|
||||||
with {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.favorite(ap_id_or_id, user),
|
with {:ok, _fav, %{data: %{"id" => id}}} = CommonAPI.favorite(ap_id_or_id, user),
|
||||||
%Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do
|
%Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def unfav(%User{} = user, ap_id_or_id) do
|
def unfav(%User{} = user, ap_id_or_id) do
|
||||||
with {:ok, %{data: %{"id" => id}}} = CommonAPI.unfavorite(ap_id_or_id, user),
|
with {:ok, _unfav, _fav, %{data: %{"id" => id}}} = CommonAPI.unfavorite(ap_id_or_id, user),
|
||||||
%Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do
|
%Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
end
|
end
|
||||||
|
|
34
test/fixtures/mastodon-undo-like.json
vendored
Normal file
34
test/fixtures/mastodon-undo-like.json
vendored
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
{
|
||||||
|
"type": "Undo",
|
||||||
|
"signature": {
|
||||||
|
"type": "RsaSignature2017",
|
||||||
|
"signatureValue": "fdxMfQSMwbC6wP6sh6neS/vM5879K67yQkHTbiT5Npr5wAac0y6+o3Ij+41tN3rL6wfuGTosSBTHOtta6R4GCOOhCaCSLMZKypnp1VltCzLDoyrZELnYQIC8gpUXVmIycZbREk22qWUe/w7DAFaKK4UscBlHDzeDVcA0K3Se5Sluqi9/Zh+ldAnEzj/rSEPDjrtvf5wGNf3fHxbKSRKFt90JvKK6hS+vxKUhlRFDf6/SMETw+EhwJSNW4d10yMUakqUWsFv4Acq5LW7l+HpYMvlYY1FZhNde1+uonnCyuQDyvzkff8zwtEJmAXC4RivO/VVLa17SmqheJZfI8oluVg==",
|
||||||
|
"creator": "http://mastodon.example.org/users/admin#main-key",
|
||||||
|
"created": "2018-05-19T16:36:58Z"
|
||||||
|
},
|
||||||
|
"object": {
|
||||||
|
"type": "Like",
|
||||||
|
"object": "http://localtesting.pleroma.lol/objects/eb92579d-3417-42a8-8652-2492c2d4f454",
|
||||||
|
"id": "http://mastodon.example.org/users/admin#likes/2",
|
||||||
|
"actor": "http://mastodon.example.org/users/admin"
|
||||||
|
},
|
||||||
|
"nickname": "lain",
|
||||||
|
"id": "http://mastodon.example.org/users/admin#likes/2/undo",
|
||||||
|
"actor": "http://mastodon.example.org/users/admin",
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams",
|
||||||
|
"https://w3id.org/security/v1",
|
||||||
|
{
|
||||||
|
"toot": "http://joinmastodon.org/ns#",
|
||||||
|
"sensitive": "as:sensitive",
|
||||||
|
"ostatus": "http://ostatus.org#",
|
||||||
|
"movedTo": "as:movedTo",
|
||||||
|
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
||||||
|
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
|
||||||
|
"conversation": "ostatus:conversation",
|
||||||
|
"atomUri": "ostatus:atomUri",
|
||||||
|
"Hashtag": "as:Hashtag",
|
||||||
|
"Emoji": "toot:Emoji"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -277,7 +277,7 @@ test "unliking a previously liked object" do
|
||||||
{:ok, like_activity, object} = ActivityPub.like(user, object)
|
{:ok, like_activity, object} = ActivityPub.like(user, object)
|
||||||
assert object.data["like_count"] == 1
|
assert object.data["like_count"] == 1
|
||||||
|
|
||||||
{:ok, object} = ActivityPub.unlike(user, object)
|
{:ok, _, _, object} = ActivityPub.unlike(user, object)
|
||||||
assert object.data["like_count"] == 0
|
assert object.data["like_count"] == 0
|
||||||
|
|
||||||
assert Repo.get(Activity, like_activity.id) == nil
|
assert Repo.get(Activity, like_activity.id) == nil
|
||||||
|
|
|
@ -153,6 +153,43 @@ test "it works for incoming likes" do
|
||||||
assert data["object"] == activity.data["object"]["id"]
|
assert data["object"] == activity.data["object"]["id"]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it returns an error for incoming unlikes wihout a like activity" do
|
||||||
|
user = insert(:user)
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"})
|
||||||
|
|
||||||
|
data =
|
||||||
|
File.read!("test/fixtures/mastodon-undo-like.json")
|
||||||
|
|> Poison.decode!()
|
||||||
|
|> Map.put("object", activity.data["object"]["id"])
|
||||||
|
|
||||||
|
assert Transmogrifier.handle_incoming(data) == :error
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it works for incoming unlikes with an existing like activity" do
|
||||||
|
user = insert(:user)
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"})
|
||||||
|
|
||||||
|
like_data =
|
||||||
|
File.read!("test/fixtures/mastodon-like.json")
|
||||||
|
|> Poison.decode!()
|
||||||
|
|> Map.put("object", activity.data["object"]["id"])
|
||||||
|
|
||||||
|
{:ok, %Activity{data: like_data, local: false}} = Transmogrifier.handle_incoming(like_data)
|
||||||
|
|
||||||
|
data =
|
||||||
|
File.read!("test/fixtures/mastodon-undo-like.json")
|
||||||
|
|> Poison.decode!()
|
||||||
|
|> Map.put("object", like_data)
|
||||||
|
|> Map.put("actor", like_data["actor"])
|
||||||
|
|
||||||
|
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||||
|
|
||||||
|
assert data["actor"] == "http://mastodon.example.org/users/admin"
|
||||||
|
assert data["type"] == "Undo"
|
||||||
|
assert data["id"] == "http://mastodon.example.org/users/admin#likes/2/undo"
|
||||||
|
assert data["object"]["id"] == "http://mastodon.example.org/users/admin#likes/2"
|
||||||
|
end
|
||||||
|
|
||||||
test "it works for incoming announces" do
|
test "it works for incoming announces" do
|
||||||
data = File.read!("test/fixtures/mastodon-announce.json") |> Poison.decode!()
|
data = File.read!("test/fixtures/mastodon-announce.json") |> Poison.decode!()
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue