Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85826730
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Authored By
Unknown
Size
6 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
Mime Type
text/x-diff
Expires
Fri, Oct 30, 9:42 AM (17 h, 41 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1799977
Default Alt Text
(6 KB)
Attached To
Mode
rPUBE pleroma-upstream
Attached
Detach File
Event Timeline