Support Undo like activities (Fix #139)
authorThog <me@thog.eu>
Sat, 19 May 2018 13:22:43 +0000 (15:22 +0200)
committerThog <me@thog.eu>
Sat, 19 May 2018 20:14:15 +0000 (22:14 +0200)
lib/pleroma/formatter.ex
lib/pleroma/web/activity_pub/activity_pub.ex
lib/pleroma/web/activity_pub/transmogrifier.ex
lib/pleroma/web/activity_pub/utils.ex
lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
lib/pleroma/web/twitter_api/twitter_api.ex
test/fixtures/mastodon-undo-like.json [new file with mode: 0644]
test/web/activity_pub/activity_pub_test.exs
test/web/activity_pub/transmogrifier_test.exs

index 395a0ac556dfc0eeb019659990517782292f6b25..53e2c204f30c0e6c542b00b63485ab0555ae7fa8 100644 (file)
@@ -160,7 +160,7 @@ defmodule Pleroma.Formatter 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
index 973d18e52ba4fc24717d3670d6886f8232248959..4e97693a2ce705d816c0f020f5e05efe33a41349 100644 (file)
@@ -131,11 +131,19 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     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
index c10d27dcde27e994062288c83cc5eb9eeb6de091..a31452a638b25f4f710022a08d2d171239a47997 100644 (file)
@@ -241,6 +241,24 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     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
index d92db0d5f2d53b69ad8a88275d41c84cceb7a00d..937f032c35e12e78fd9e2938fc91957b3f3ae501 100644 (file)
@@ -315,6 +315,23 @@ defmodule Pleroma.Web.ActivityPub.Utils do
     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)
index 5475cb505db40749f1a3bfae4b45d5e3ce8bc940..b218c269d0fa59331bb031be0cd388c2c3179557 100644 (file)
@@ -323,7 +323,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController 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
index 8177a49888d07a07b09952b1b783c8d50bdd6deb..722e436e22e7f23b7aa16c75bfa7f489804bb53d 100644 (file)
@@ -82,14 +82,14 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI 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 (file)
index 0000000..0cbed30
--- /dev/null
@@ -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
index c1ba626b7c2f145f7cffe91619dade21ca574ba3..9adce84b57f7ef67eb55b2d81d911a768b6805c6 100644 (file)
@@ -277,7 +277,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest 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
index d8579ecfec6d5e5569dd6df9c823e39d5c11c35f..a0af75a698de277298a1609250680373c4a2be80 100644 (file)
@@ -153,6 +153,43 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest 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!()