From 6f39ecc41be77298ae375ca0a2f51ae7dc29fbbf Mon Sep 17 00:00:00 2001 From: Thog Date: Sat, 19 May 2018 15:22:43 +0200 Subject: [PATCH] Support Undo like activities (Fix #139) --- lib/pleroma/formatter.ex | 2 +- lib/pleroma/web/activity_pub/activity_pub.ex | 18 ++++++--- .../web/activity_pub/transmogrifier.ex | 18 +++++++++ lib/pleroma/web/activity_pub/utils.ex | 17 +++++++++ .../mastodon_api/mastodon_api_controller.ex | 2 +- lib/pleroma/web/twitter_api/twitter_api.ex | 4 +- test/fixtures/mastodon-undo-like.json | 34 +++++++++++++++++ test/web/activity_pub/activity_pub_test.exs | 2 +- test/web/activity_pub/transmogrifier_test.exs | 37 +++++++++++++++++++ 9 files changed, 124 insertions(+), 10 deletions(-) create mode 100644 test/fixtures/mastodon-undo-like.json diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex index 395a0ac55..53e2c204f 100644 --- a/lib/pleroma/formatter.ex +++ b/lib/pleroma/formatter.ex @@ -160,7 +160,7 @@ def add_links({subs, text}) do links = Regex.scan(@link_regex, text) |> 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 = links diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 973d18e52..4e97693a2 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -131,11 +131,19 @@ def like( end end - def unlike(%User{} = actor, %Object{} = object) do - with %Activity{} = activity <- get_existing_like(actor.ap_id, object), - {:ok, _activity} <- Repo.delete(activity), - {:ok, object} <- remove_like_from_object(activity, object) do - {:ok, object} + def unlike( + %User{} = actor, + %Object{} = object, + activity_id \\ nil, + 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 _e -> {:ok, object} end diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index c10d27dcd..a31452a63 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -241,6 +241,24 @@ def handle_incoming( 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 # Accept # Undo for non-Announce diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index d92db0d5f..937f032c3 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -315,6 +315,23 @@ def make_unannounce_data( if activity_id, do: Map.put(data, "id", activity_id), else: data 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 with announcements <- [actor | object.data["announcements"] || []] |> Enum.uniq() do update_element_in_object("announcement", announcements, object) diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index 5475cb505..b218c269d 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -323,7 +323,7 @@ def fav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do end 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 render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity}) end diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index 8177a4988..722e436e2 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -82,14 +82,14 @@ defp unrepeat(%User{} = user, ap_id_or_id) do end 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 {:ok, activity} end end 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 {:ok, activity} end diff --git a/test/fixtures/mastodon-undo-like.json b/test/fixtures/mastodon-undo-like.json new file mode 100644 index 000000000..0cbed30ff --- /dev/null +++ b/test/fixtures/mastodon-undo-like.json @@ -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" + } + ] +} \ No newline at end of file diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs index c1ba626b7..9adce84b5 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -277,7 +277,7 @@ test "unliking a previously liked object" do {:ok, like_activity, object} = ActivityPub.like(user, object) 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 Repo.get(Activity, like_activity.id) == nil diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index d8579ecfe..a0af75a69 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -153,6 +153,43 @@ test "it works for incoming likes" do assert data["object"] == activity.data["object"]["id"] 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 data = File.read!("test/fixtures/mastodon-announce.json") |> Poison.decode!()