Merge branch 'develop' of https://git.pleroma.social/pleroma/pleroma into feature...
authorFrancis Dinh <normandy@firemail.cc>
Fri, 20 Apr 2018 20:47:44 +0000 (16:47 -0400)
committerFrancis Dinh <normandy@firemail.cc>
Fri, 20 Apr 2018 20:48:18 +0000 (16:48 -0400)
lib/pleroma/web/activity_pub/activity_pub.ex
lib/pleroma/web/activity_pub/utils.ex
lib/pleroma/web/common_api/common_api.ex
lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
lib/pleroma/web/router.ex
lib/pleroma/web/twitter_api/twitter_api.ex
lib/pleroma/web/twitter_api/twitter_api_controller.ex
test/web/activity_pub/activity_pub_test.exs
test/web/mastodon_api/mastodon_api_controller_test.exs

index 04b50c1cc0fc32870dad268d569648c6e8d60263..dccd2e7577c2c8c97497182bc03915ba2f800a1f 100644 (file)
@@ -140,6 +140,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     end
   end
 
+  def unannounce(%User{} = actor, %Object{} = object, local \\ true) do
+    with %Activity{} = activity <- get_existing_announce(actor.ap_id, object),
+         unannounce_data <- make_unannounce_data(actor, object),
+         {:ok, unannounce_activity} <- insert(unannounce_data, local),
+         {:ok, _activity} <- Repo.delete(activity),
+         {:ok, object} <- remove_announce_from_object(activity, object) do
+      {:ok, unannounce_activity, activity, object}
+    else
+      _e -> {:ok, object}
+    end
+  end
+
   def follow(follower, followed, activity_id \\ nil, local \\ true) do
     with data <- make_follow_data(follower, followed, activity_id),
          {:ok, activity} <- insert(data, local),
index 7a0762e9f17fc7bb0bc1d49634105dcb1905faea..1f740eda551ee4f518c0fddb068c5e625463be33 100644 (file)
@@ -236,6 +236,28 @@ defmodule Pleroma.Web.ActivityPub.Utils do
 
   #### Announce-related helpers
 
+  @doc """
+  Retruns an existing announce activity if the notice has already been announced
+  """
+  def get_existing_announce(actor, %{data: %{"id" => id}}) do
+    query =
+      from(
+        activity in Activity,
+        where: fragment("(?)->>'actor' = ?", activity.data, ^actor),
+        # this is to use the index
+        where:
+          fragment(
+            "coalesce((?)->'object'->>'id', (?)->>'object') = ?",
+            activity.data,
+            activity.data,
+            ^id
+          ),
+        where: fragment("(?)->>'type' = 'Announce'", activity.data)
+      )
+
+    Repo.one(query)
+  end
+
   @doc """
   Make announce activity data for the given actor and object
   """
@@ -256,12 +278,35 @@ defmodule Pleroma.Web.ActivityPub.Utils do
     if activity_id, do: Map.put(data, "id", activity_id), else: data
   end
 
+  @doc """
+  Make unannounce activity data for the given actor and object
+  """
+  def make_unannounce_data(
+        %User{ap_id: ap_id} = user,
+        %Object{data: %{"id" => id, "context" => context}} = object
+      ) do
+    %{
+      "type" => "Undo",
+      "actor" => ap_id,
+      "object" => id,
+      "to" => [user.follower_address, object.data["actor"]],
+      "cc" => ["https://www.w3.org/ns/activitystreams#Public"],
+      "context" => context
+    }
+  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)
     end
   end
 
+  def remove_announce_from_object(%Activity{data: %{"actor" => actor}}, object) do
+    with announcements <- (object.data["announcements"] || []) |> List.delete(actor) do
+      update_element_in_object("announcement", announcements, object)
+    end
+  end
+
   #### Unfollow-related helpers
 
   def make_unfollow_data(follower, followed, follow_activity) do
index 21225c3b7d8a95b908b2820a795433dec25fdf0a..8889b9b4206c0c5c733aaccfb36d0f26cb2b5f6d 100644 (file)
@@ -24,6 +24,16 @@ defmodule Pleroma.Web.CommonAPI do
     end
   end
 
+  def unrepeat(id_or_ap_id, user) do
+    with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
+         object <- Object.get_by_ap_id(activity.data["object"]["id"]) do
+      ActivityPub.unannounce(user, object)
+    else
+      _ ->
+        {:error, "Could not unrepeat"}
+    end
+  end
+
   def favorite(id_or_ap_id, user) do
     with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
          false <- activity.data["actor"] == user.ap_id,
index 8b3492332c23340a6ee5a25c4c6e1fdcf9436931..a49be058865cc76e4a2c6f9546840ae5a36479ef 100644 (file)
@@ -296,6 +296,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
     end
   end
 
+  def unreblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
+    with {:ok, _, _, %{data: %{"id" => id}}} = CommonAPI.unrepeat(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
+  end
+
   def fav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
     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
index 8f63fdc7072a163c5693b41108974add56b416c9..56dc6533b13b151559e6845841350eb927811f9f 100644 (file)
@@ -110,6 +110,7 @@ defmodule Pleroma.Web.Router do
     delete("/statuses/:id", MastodonAPIController, :delete_status)
 
     post("/statuses/:id/reblog", MastodonAPIController, :reblog_status)
+    post("/statuses/:id/unreblog", MastodonAPIController, :unreblog_status)
     post("/statuses/:id/favourite", MastodonAPIController, :fav_status)
     post("/statuses/:id/unfavourite", MastodonAPIController, :unfav_status)
 
index c12cd7f8aa14a8912e89b69e04609a9e31897b38..b6ae7c7f70f285ee01c2e1d8168eeb4b51e02ab6 100644 (file)
@@ -12,6 +12,18 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
     CommonAPI.post(user, data)
   end
 
+  def delete(%User{} = user, id) do
+    # TwitterAPI does not have an "unretweet" endpoint; instead this is done
+    # via the "destroy" endpoint.  Therefore, there is a need to handle
+    # when the status to "delete" is actually an Announce (repeat) object.
+    with %Activity{data: %{"type" => type}} <- Repo.get(Activity, id) do
+      case type do
+        "Announce" -> unrepeat(user, id)
+        _ -> CommonAPI.delete(id, user)
+      end
+    end
+  end
+
   def follow(%User{} = follower, params) do
     with {:ok, %User{} = followed} <- get_user(params),
          {:ok, follower} <- User.follow(follower, followed),
@@ -64,6 +76,12 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
     end
   end
 
+  defp unrepeat(%User{} = user, ap_id_or_id) do
+    with {:ok, _unannounce, activity, _object} <- CommonAPI.unrepeat(ap_id_or_id, user) do
+      {:ok, activity}
+    end
+  end
+
   def fav(%User{} = user, ap_id_or_id) do
     with {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.favorite(ap_id_or_id, user),
          %Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do
index 6cf8682b882033147c34d844e4c3d69c3f23b2a4..429a0b7ac804148194384bcc0101bc731f1eba3c 100644 (file)
@@ -157,8 +157,8 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
   end
 
   def delete_post(%{assigns: %{user: user}} = conn, %{"id" => id}) do
-    with {:ok, delete} <- CommonAPI.delete(id, user) do
-      render(conn, ActivityView, "activity.json", %{activity: delete, for: user})
+    with {:ok, activity} <- TwitterAPI.delete(user, id) do
+      render(conn, ActivityView, "activity.json", %{activity: activity, for: user})
     end
   end
 
index 657d75a554c08f3c6825f227b71e15b66830fdd2..6a07da775242c545488b58134233da9456e9c48b 100644 (file)
@@ -271,6 +271,37 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     end
   end
 
+  describe "unannouncing an object" do
+    test "unannouncing a previously announced object" do
+      note_activity = insert(:note_activity)
+      object = Object.get_by_ap_id(note_activity.data["object"]["id"])
+      user = insert(:user)
+
+      # Unannouncing an object that is not announced does nothing
+      #{:ok, object} = ActivityPub.unannounce(user, object)
+      #assert object.data["announcement_count"] == 0
+
+      {:ok, announce_activity, object} = ActivityPub.announce(user, object)
+      assert object.data["announcement_count"] == 1
+
+      {:ok, unannounce_activity, activity, object} = ActivityPub.unannounce(user, object)
+      assert object.data["announcement_count"] == 0
+
+      assert activity == announce_activity
+
+      assert unannounce_activity.data["to"] == [
+               User.ap_followers(user),
+               note_activity.data["actor"]
+      ]
+      assert unannounce_activity.data["type"] == "Undo"
+      assert unannounce_activity.data["object"] == object.data["id"]
+      assert unannounce_activity.data["actor"] == user.ap_id
+      assert unannounce_activity.data["context"] == object.data["context"]
+
+      assert Repo.get(Activity, announce_activity.id) == nil
+    end
+  end
+
   describe "uploading files" do
     test "copies the file to the configured folder" do
       file = %Plug.Upload{
index 5293b9364ee375441a130f0f121e22c5c1718a87..2a24037d7b5b5fdb359845e9014e2fe43301fcf2 100644 (file)
@@ -264,6 +264,25 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
     end
   end
 
+  describe "unreblogging" do
+    test "unreblogs and returns the unreblogged status", %{conn: conn} do
+      activity = insert(:note_activity)
+      user = insert(:user)
+
+      {:ok, _, _} = CommonAPI.repeat(activity.id, user)
+
+      conn =
+        conn
+        |> assign(:user, user)
+        |> post("/api/v1/statuses/#{activity.id}/unreblog")
+
+      assert %{"id" => id, "reblogged" => false, "reblogs_count" => 0} = 
+               json_response(conn, 200)
+
+      assert to_string(activity.id) == id
+    end
+  end
+
   describe "favoriting" do
     test "favs a status and returns it", %{conn: conn} do
       activity = insert(:note_activity)