Merge branch 'activitypub-likes' into 'develop'
Activitypub c2s likes See merge request pleroma/pleroma!658
This commit is contained in:
commit
fa0392e49c
|
@ -54,6 +54,36 @@ def object(conn, %{"uuid" => uuid}) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def object_likes(conn, %{"uuid" => uuid, "page" => page}) do
|
||||||
|
with ap_id <- o_status_url(conn, :object, uuid),
|
||||||
|
%Object{} = object <- Object.get_cached_by_ap_id(ap_id),
|
||||||
|
{_, true} <- {:public?, ActivityPub.is_public?(object)},
|
||||||
|
likes <- Utils.get_object_likes(object) do
|
||||||
|
{page, _} = Integer.parse(page)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_resp_header("content-type", "application/activity+json")
|
||||||
|
|> json(ObjectView.render("likes.json", ap_id, likes, page))
|
||||||
|
else
|
||||||
|
{:public?, false} ->
|
||||||
|
{:error, :not_found}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def object_likes(conn, %{"uuid" => uuid}) do
|
||||||
|
with ap_id <- o_status_url(conn, :object, uuid),
|
||||||
|
%Object{} = object <- Object.get_cached_by_ap_id(ap_id),
|
||||||
|
{_, true} <- {:public?, ActivityPub.is_public?(object)},
|
||||||
|
likes <- Utils.get_object_likes(object) do
|
||||||
|
conn
|
||||||
|
|> put_resp_header("content-type", "application/activity+json")
|
||||||
|
|> json(ObjectView.render("likes.json", ap_id, likes))
|
||||||
|
else
|
||||||
|
{:public?, false} ->
|
||||||
|
{:error, :not_found}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def activity(conn, %{"uuid" => uuid}) do
|
def activity(conn, %{"uuid" => uuid}) do
|
||||||
with ap_id <- o_status_url(conn, :activity, uuid),
|
with ap_id <- o_status_url(conn, :activity, uuid),
|
||||||
%Activity{} = activity <- Activity.normalize(ap_id),
|
%Activity{} = activity <- Activity.normalize(ap_id),
|
||||||
|
@ -204,6 +234,15 @@ def handle_user_activity(user, %{"type" => "Delete"} = params) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_user_activity(user, %{"type" => "Like"} = params) do
|
||||||
|
with %Object{} = object <- Object.normalize(params["object"]),
|
||||||
|
{:ok, activity, _object} <- ActivityPub.like(user, object) do
|
||||||
|
{:ok, activity}
|
||||||
|
else
|
||||||
|
_ -> {:error, "Can't like object"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def handle_user_activity(_, _) do
|
def handle_user_activity(_, _) do
|
||||||
{:error, "Unhandled activity type"}
|
{:error, "Unhandled activity type"}
|
||||||
end
|
end
|
||||||
|
|
|
@ -629,6 +629,7 @@ def prepare_object(object) do
|
||||||
|> add_mention_tags
|
|> add_mention_tags
|
||||||
|> add_emoji_tags
|
|> add_emoji_tags
|
||||||
|> add_attributed_to
|
|> add_attributed_to
|
||||||
|
|> add_likes
|
||||||
|> prepare_attachments
|
|> prepare_attachments
|
||||||
|> set_conversation
|
|> set_conversation
|
||||||
|> set_reply_to_uri
|
|> set_reply_to_uri
|
||||||
|
@ -641,7 +642,7 @@ def prepare_object(object) do
|
||||||
# internal -> Mastodon
|
# internal -> Mastodon
|
||||||
# """
|
# """
|
||||||
|
|
||||||
def prepare_outgoing(%{"type" => "Create", "object" => %{"type" => "Note"} = object} = data) do
|
def prepare_outgoing(%{"type" => "Create", "object" => object} = data) do
|
||||||
object =
|
object =
|
||||||
object
|
object
|
||||||
|> prepare_object
|
|> prepare_object
|
||||||
|
@ -788,6 +789,22 @@ def add_attributed_to(object) do
|
||||||
|> Map.put("attributedTo", attributedTo)
|
|> Map.put("attributedTo", attributedTo)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def add_likes(%{"id" => id, "like_count" => likes} = object) do
|
||||||
|
likes = %{
|
||||||
|
"id" => "#{id}/likes",
|
||||||
|
"first" => "#{id}/likes?page=1",
|
||||||
|
"type" => "OrderedCollection",
|
||||||
|
"totalItems" => likes
|
||||||
|
}
|
||||||
|
|
||||||
|
object
|
||||||
|
|> Map.put("likes", likes)
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_likes(object) do
|
||||||
|
object
|
||||||
|
end
|
||||||
|
|
||||||
def prepare_attachments(object) do
|
def prepare_attachments(object) do
|
||||||
attachments =
|
attachments =
|
||||||
(object["attachment"] || [])
|
(object["attachment"] || [])
|
||||||
|
@ -803,7 +820,6 @@ def prepare_attachments(object) do
|
||||||
defp strip_internal_fields(object) do
|
defp strip_internal_fields(object) do
|
||||||
object
|
object
|
||||||
|> Map.drop([
|
|> Map.drop([
|
||||||
"likes",
|
|
||||||
"like_count",
|
"like_count",
|
||||||
"announcements",
|
"announcements",
|
||||||
"announcement_count",
|
"announcement_count",
|
||||||
|
|
|
@ -231,6 +231,27 @@ def get_existing_like(actor, %{data: %{"id" => id}}) do
|
||||||
Repo.one(query)
|
Repo.one(query)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns like activities targeting an object
|
||||||
|
"""
|
||||||
|
def get_object_likes(%{data: %{"id" => id}}) do
|
||||||
|
query =
|
||||||
|
from(
|
||||||
|
activity in Activity,
|
||||||
|
# this is to use the index
|
||||||
|
where:
|
||||||
|
fragment(
|
||||||
|
"coalesce((?)->'object'->>'id', (?)->>'object') = ?",
|
||||||
|
activity.data,
|
||||||
|
activity.data,
|
||||||
|
^id
|
||||||
|
),
|
||||||
|
where: fragment("(?)->>'type' = 'Like'", activity.data)
|
||||||
|
)
|
||||||
|
|
||||||
|
Repo.all(query)
|
||||||
|
end
|
||||||
|
|
||||||
def make_like_data(%User{ap_id: ap_id} = actor, %{data: %{"id" => id}} = object, activity_id) do
|
def make_like_data(%User{ap_id: ap_id} = actor, %{data: %{"id" => id}} = object, activity_id) do
|
||||||
data = %{
|
data = %{
|
||||||
"type" => "Like",
|
"type" => "Like",
|
||||||
|
|
|
@ -35,4 +35,38 @@ def render("object.json", %{object: %Activity{} = activity}) do
|
||||||
|
|
||||||
Map.merge(base, additional)
|
Map.merge(base, additional)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def render("likes.json", ap_id, likes, page) do
|
||||||
|
collection(likes, "#{ap_id}/likes", page)
|
||||||
|
|> Map.merge(Pleroma.Web.ActivityPub.Utils.make_json_ld_header())
|
||||||
|
end
|
||||||
|
|
||||||
|
def render("likes.json", ap_id, likes) do
|
||||||
|
%{
|
||||||
|
"id" => "#{ap_id}/likes",
|
||||||
|
"type" => "OrderedCollection",
|
||||||
|
"totalItems" => length(likes),
|
||||||
|
"first" => collection(likes, "#{ap_id}/followers", 1)
|
||||||
|
}
|
||||||
|
|> Map.merge(Pleroma.Web.ActivityPub.Utils.make_json_ld_header())
|
||||||
|
end
|
||||||
|
|
||||||
|
def collection(collection, iri, page) do
|
||||||
|
offset = (page - 1) * 10
|
||||||
|
items = Enum.slice(collection, offset, 10)
|
||||||
|
items = Enum.map(items, fn object -> Transmogrifier.prepare_object(object.data) end)
|
||||||
|
total = length(collection)
|
||||||
|
|
||||||
|
map = %{
|
||||||
|
"id" => "#{iri}?page=#{page}",
|
||||||
|
"type" => "OrderedCollectionPage",
|
||||||
|
"partOf" => iri,
|
||||||
|
"totalItems" => total,
|
||||||
|
"orderedItems" => items
|
||||||
|
}
|
||||||
|
|
||||||
|
if offset < total do
|
||||||
|
Map.put(map, "next", "#{iri}?page=#{page + 1}")
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -421,6 +421,7 @@ defmodule Pleroma.Web.Router do
|
||||||
get("/users/:nickname/followers", ActivityPubController, :followers)
|
get("/users/:nickname/followers", ActivityPubController, :followers)
|
||||||
get("/users/:nickname/following", ActivityPubController, :following)
|
get("/users/:nickname/following", ActivityPubController, :following)
|
||||||
get("/users/:nickname/outbox", ActivityPubController, :outbox)
|
get("/users/:nickname/outbox", ActivityPubController, :outbox)
|
||||||
|
get("/objects/:uuid/likes", ActivityPubController, :object_likes)
|
||||||
end
|
end
|
||||||
|
|
||||||
pipeline :activitypub_client do
|
pipeline :activitypub_client do
|
||||||
|
|
|
@ -57,6 +57,11 @@ def direct_note_factory do
|
||||||
%Pleroma.Object{data: Map.merge(data, %{"to" => [user2.ap_id]})}
|
%Pleroma.Object{data: Map.merge(data, %{"to" => [user2.ap_id]})}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def article_factory do
|
||||||
|
note_factory()
|
||||||
|
|> Map.put("type", "Article")
|
||||||
|
end
|
||||||
|
|
||||||
def tombstone_factory do
|
def tombstone_factory do
|
||||||
data = %{
|
data = %{
|
||||||
"type" => "Tombstone",
|
"type" => "Tombstone",
|
||||||
|
@ -110,6 +115,26 @@ def note_activity_factory do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def article_activity_factory do
|
||||||
|
article = insert(:article)
|
||||||
|
|
||||||
|
data = %{
|
||||||
|
"id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
|
||||||
|
"type" => "Create",
|
||||||
|
"actor" => article.data["actor"],
|
||||||
|
"to" => article.data["to"],
|
||||||
|
"object" => article.data,
|
||||||
|
"published" => DateTime.utc_now() |> DateTime.to_iso8601(),
|
||||||
|
"context" => article.data["context"]
|
||||||
|
}
|
||||||
|
|
||||||
|
%Pleroma.Activity{
|
||||||
|
data: data,
|
||||||
|
actor: data["actor"],
|
||||||
|
recipients: data["to"]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
def announce_activity_factory do
|
def announce_activity_factory do
|
||||||
note_activity = insert(:note_activity)
|
note_activity = insert(:note_activity)
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
|
@ -89,6 +89,21 @@ test "it returns 404 for tombstone objects", %{conn: conn} do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "/object/:uuid/likes" do
|
||||||
|
test "it returns the like activities in a collection", %{conn: conn} do
|
||||||
|
like = insert(:like_activity)
|
||||||
|
uuid = String.split(like.data["object"], "/") |> List.last()
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> put_req_header("accept", "application/activity+json")
|
||||||
|
|> get("/objects/#{uuid}/likes")
|
||||||
|
|> json_response(200)
|
||||||
|
|
||||||
|
assert List.first(result["first"]["orderedItems"])["id"] == like.data["id"]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "/activities/:uuid" do
|
describe "/activities/:uuid" do
|
||||||
test "it returns a json representation of the activity", %{conn: conn} do
|
test "it returns a json representation of the activity", %{conn: conn} do
|
||||||
activity = insert(:note_activity)
|
activity = insert(:note_activity)
|
||||||
|
@ -292,6 +307,31 @@ test "it rejects delete activity of object from other actor", %{conn: conn} do
|
||||||
|
|
||||||
assert json_response(conn, 400)
|
assert json_response(conn, 400)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it increases like count when receiving a like action", %{conn: conn} do
|
||||||
|
note_activity = insert(:note_activity)
|
||||||
|
user = User.get_cached_by_ap_id(note_activity.data["actor"])
|
||||||
|
|
||||||
|
data = %{
|
||||||
|
type: "Like",
|
||||||
|
object: %{
|
||||||
|
id: note_activity.data["object"]["id"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> put_req_header("content-type", "application/activity+json")
|
||||||
|
|> post("/users/#{user.nickname}/outbox", data)
|
||||||
|
|
||||||
|
result = json_response(conn, 201)
|
||||||
|
assert Activity.get_by_ap_id(result["id"])
|
||||||
|
|
||||||
|
object = Object.get_by_ap_id(note_activity.data["object"]["id"])
|
||||||
|
assert object
|
||||||
|
assert object.data["like_count"] == 1
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "/users/:nickname/followers" do
|
describe "/users/:nickname/followers" do
|
||||||
|
|
|
@ -829,12 +829,33 @@ test "it strips internal fields" do
|
||||||
assert length(modified["object"]["tag"]) == 2
|
assert length(modified["object"]["tag"]) == 2
|
||||||
|
|
||||||
assert is_nil(modified["object"]["emoji"])
|
assert is_nil(modified["object"]["emoji"])
|
||||||
assert is_nil(modified["object"]["likes"])
|
|
||||||
assert is_nil(modified["object"]["like_count"])
|
assert is_nil(modified["object"]["like_count"])
|
||||||
assert is_nil(modified["object"]["announcements"])
|
assert is_nil(modified["object"]["announcements"])
|
||||||
assert is_nil(modified["object"]["announcement_count"])
|
assert is_nil(modified["object"]["announcement_count"])
|
||||||
assert is_nil(modified["object"]["context_id"])
|
assert is_nil(modified["object"]["context_id"])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it strips internal fields of article" do
|
||||||
|
activity = insert(:article_activity)
|
||||||
|
|
||||||
|
{:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
|
||||||
|
|
||||||
|
assert length(modified["object"]["tag"]) == 2
|
||||||
|
|
||||||
|
assert is_nil(modified["object"]["emoji"])
|
||||||
|
assert is_nil(modified["object"]["like_count"])
|
||||||
|
assert is_nil(modified["object"]["announcements"])
|
||||||
|
assert is_nil(modified["object"]["announcement_count"])
|
||||||
|
assert is_nil(modified["object"]["context_id"])
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it adds like collection to object" do
|
||||||
|
activity = insert(:note_activity)
|
||||||
|
{:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
|
||||||
|
|
||||||
|
assert modified["object"]["likes"]["type"] == "OrderedCollection"
|
||||||
|
assert modified["object"]["likes"]["totalItems"] == 0
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "user upgrade" do
|
describe "user upgrade" do
|
||||||
|
|
Loading…
Reference in New Issue