Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
6 KB
Referenced Files
None
Subscribers
None
diff --git a/changelog.d/mastodon-quote-entity.add b/changelog.d/mastodon-quote-entity.add
new file mode 100644
index 000000000..dc9eccd97
--- /dev/null
+++ b/changelog.d/mastodon-quote-entity.add
@@ -0,0 +1 @@
+Add the Mastodon quote entity (`quote` with `state` and `quoted_status`) to statuses, so Mastodon 4.4+ clients show quote posts
diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex
index 63e9eebf0..cab019f37 100644
--- a/lib/pleroma/web/api_spec/schemas/status.ex
+++ b/lib/pleroma/web/api_spec/schemas/status.ex
@@ -234,6 +234,29 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
type: :integer,
description: "How many statuses quoted this status."
},
+ quote: %Schema{
+ type: :object,
+ nullable: true,
+ description:
+ "The status being quoted, if any (Mastodon 4.4+). Quoted statuses only have `quoted_status_id`.",
+ properties: %{
+ state: %Schema{
+ type: :string,
+ enum: ["accepted", "unauthorized", "deleted"],
+ description: "Whether the quoted status can be shown"
+ },
+ quoted_status: %Schema{
+ allOf: [%OpenApiSpex.Reference{"$ref": "#/components/schemas/Status"}],
+ nullable: true,
+ description: "The quoted status, if it can be shown"
+ },
+ quoted_status_id: %Schema{
+ type: :string,
+ nullable: true,
+ description: "ID of the quoted status, if it can be shown"
+ }
+ }
+ },
reblog: %Schema{
allOf: [%OpenApiSpex.Reference{"$ref": "#/components/schemas/Status"}],
nullable: true,
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index 6a5d627a5..42cbc1590 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -455,6 +455,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
language: get_language(object),
emojis: build_emojis(object.data["emoji"]),
quotes_count: object.data["quotesCount"] || 0,
+ quote: build_quote(object, quote_activity, quote_post, opts),
pleroma: %{
local: activity.local,
conversation_id: get_context_id(activity),
@@ -691,7 +692,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
%{ancestors: ancestors, descendants: descendants} =
activities
|> Enum.reverse()
- |> Enum.group_by(fn %{id: id} -> if id < activity.id, do: :ancestors, else: :descendants end)
+ |> Enum.group_by(fn %{id: id} ->
+ if id < activity.id, do: :ancestors, else: :descendants
+ end)
|> Map.put_new(:ancestors, [])
|> Map.put_new(:descendants, [])
@@ -742,6 +745,26 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
end
end
+ # Mastodon (4.4+) quote entity. Quoted statuses get a shallow quote that only
+ # has the id, as quotes are only rendered one level deep.
+ defp build_quote(%Object{data: %{"quoteUrl" => url}}, quote_activity, quote_post, opts)
+ when is_binary(url) and url != "" do
+ state =
+ cond do
+ is_nil(quote_activity) -> "deleted"
+ visible_for_user?(quote_activity, opts[:for]) -> "accepted"
+ true -> "unauthorized"
+ end
+
+ if opts[:show_quote] == false do
+ %{state: state, quoted_status_id: if(state == "accepted", do: to_string(quote_activity.id))}
+ else
+ %{state: state, quoted_status: if(state == "accepted", do: quote_post)}
+ end
+ end
+
+ defp build_quote(_object, _quote_activity, _quote_post, _opts), do: nil
+
def get_quote(activity, %{quoted_activities: quoted_activities}) do
object = Object.normalize(activity, fetch: false)
diff --git a/test/pleroma/web/mastodon_api/views/status_view_test.exs b/test/pleroma/web/mastodon_api/views/status_view_test.exs
index 763eb1599..6d1bda193 100644
--- a/test/pleroma/web/mastodon_api/views/status_view_test.exs
+++ b/test/pleroma/web/mastodon_api/views/status_view_test.exs
@@ -345,7 +345,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
bookmark_folder: nil,
list_id: nil
},
- quotes_count: 0
+ quotes_count: 0,
+ quote: nil
}
assert status == expected
@@ -507,6 +508,63 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
refute status.pleroma.quote_visible
end
+ describe "Mastodon quote entity" do
+ test "a quote has the quoted status, and quoted statuses only a shallow quote" do
+ post = insert(:note_activity)
+ user = insert(:user)
+
+ {:ok, quote_post} = CommonAPI.post(user, %{status: "he", quoted_status_id: post.id})
+
+ {:ok, quoted_quote_post} =
+ CommonAPI.post(user, %{status: "yo", quoted_status_id: quote_post.id})
+
+ status = StatusView.render("show.json", %{activity: quoted_quote_post})
+
+ assert %{state: "accepted", quoted_status: %{id: quoted_id} = quoted} = status.quote
+ assert quoted_id == to_string(quote_post.id)
+ assert quoted.quote == %{state: "accepted", quoted_status_id: to_string(post.id)}
+
+ assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
+ end
+
+ test "a quote of a post the user cannot see is unauthorized" do
+ user = insert(:user)
+ private = insert(:followers_only_note_activity, user: user)
+
+ quote_private =
+ insert(:note_activity,
+ note: insert(:note, data: %{"quoteUrl" => Object.normalize(private).data["id"]})
+ )
+
+ status = StatusView.render("show.json", %{activity: quote_private})
+ assert status.quote == %{state: "unauthorized", quoted_status: nil}
+ assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
+
+ follower = insert(:user)
+ CommonAPI.follow(user, follower)
+
+ status = StatusView.render("show.json", %{activity: quote_private, for: follower})
+ assert %{state: "accepted", quoted_status: %{id: id}} = status.quote
+ assert id == to_string(private.id)
+ end
+
+ test "a quote of a missing post is deleted" do
+ quote_missing =
+ insert(:note_activity,
+ note: insert(:note, data: %{"quoteUrl" => "https://example.com/objects/gone"})
+ )
+
+ status = StatusView.render("show.json", %{activity: quote_missing})
+ assert status.quote == %{state: "deleted", quoted_status: nil}
+ end
+
+ test "a post without a quote has no quote" do
+ status = StatusView.render("show.json", %{activity: insert(:note_activity)})
+ assert status.quote == nil
+ assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
+ end
+ end
+
test "repost of quote post" do
post = insert(:note_activity)
user = insert(:user)

File Metadata

Mime Type
text/x-diff
Expires
Fri, Oct 30, 9:42 AM (18 h, 39 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1799977
Default Alt Text
(6 KB)

Event Timeline