Verified Commit 6f39ecc4 authored by Thog's avatar Thog
Browse files

Support Undo like activities (Fix #139)

parent 7831b387
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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)
......
......@@ -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
......
......@@ -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
......
{
"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
......@@ -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
......
......@@ -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!()
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment