commit: d7b0115124d1968a817c944001455f691d9b09ff
parent ca03d94f52f3494c767267a31840224b23c7b1b4
Author: nicole mikołajczyk <me@mkljczk.pl>
Date: Tue, 2 Dec 2025 14:34:16 +0100
Merge branch 'mastodon-quotes-updates' into 'develop'
Use Mastodon-compatible route for quotes list and param for quotes count
See merge request pleroma/pleroma!4367
Diffstat:
12 files changed, 137 insertions(+), 81 deletions(-)
diff --git a/changelog.d/mastodon-quotes-updates.change b/changelog.d/mastodon-quotes-updates.change
@@ -0,0 +1 @@
+Use Mastodon-compatible route for quotes list and param for quotes count
diff --git a/docs/development/API/differences_in_mastoapi_responses.md b/docs/development/API/differences_in_mastoapi_responses.md
@@ -39,7 +39,6 @@ Has these additional fields under the `pleroma` object:
- `emoji_reactions`: A list with emoji / reaction maps. The format is `{name: "☕", count: 1, me: true}`. Contains no information about the reacting users, for that use the `/statuses/:id/reactions` endpoint.
- `parent_visible`: If the parent of this post is visible to the user or not.
- `pinned_at`: a datetime (iso8601) when status was pinned, `null` otherwise.
-- `quotes_count`: the count of status quotes.
- `bookmark_folder`: the ID of the folder bookmark is stored within (if any).
- `list_id`: the ID of the list the post is addressed to (if any, only returned to author).
diff --git a/lib/pleroma/web/api_spec/operations/pleroma_status_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_status_operation.ex
@@ -19,7 +19,8 @@ defmodule Pleroma.Web.ApiSpec.PleromaStatusOperation do
%Operation{
tags: ["Retrieve status information"],
summary: "Quoted by",
- description: "View quotes for a given status",
+ deprecated: true,
+ description: "View quotes for a given status. Use /api/v1/statuses/:id/quotes instead.",
operationId: "PleromaAPI.StatusController.quotes",
parameters: [id_param() | pagination_params()],
security: [%{"oAuth" => ["read:statuses"]}],
diff --git a/lib/pleroma/web/api_spec/operations/status_operation.ex b/lib/pleroma/web/api_spec/operations/status_operation.ex
@@ -549,6 +549,27 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
}
end
+ def quotes_operation do
+ %Operation{
+ tags: ["Retrieve status information"],
+ summary: "Quoted by",
+ description: "View quotes for a given status",
+ operationId: "StatusController.quotes",
+ parameters: [id_param() | pagination_params()],
+ security: [%{"oAuth" => ["read:statuses"]}],
+ responses: %{
+ 200 =>
+ Operation.response(
+ "Array of Status",
+ "application/json",
+ array_of_statuses()
+ ),
+ 403 => Operation.response("Forbidden", "application/json", ApiError),
+ 404 => Operation.response("Not Found", "application/json", ApiError)
+ }
+ }
+ end
+
def array_of_statuses do
%Schema{type: :array, items: Status, example: [Status.schema().example]}
end
diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex
@@ -219,7 +219,9 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
},
quotes_count: %Schema{
type: :integer,
- description: "How many statuses quoted this status"
+ deprecated: true,
+ description:
+ "How many statuses quoted this status. Deprecated, use `quotes_count` from parent object instead."
},
local: %Schema{
type: :boolean,
@@ -259,6 +261,10 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
}
},
poll: %Schema{allOf: [Poll], nullable: true, description: "The poll attached to the status"},
+ quotes_count: %Schema{
+ type: :integer,
+ description: "How many statuses quoted this status."
+ },
reblog: %Schema{
allOf: [%OpenApiSpex.Reference{"$ref": "#/components/schemas/Status"}],
nullable: true,
@@ -385,6 +391,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
"quotes_count" => 0
},
"poll" => nil,
+ "quotes_count" => 0,
"reblog" => nil,
"reblogged" => false,
"reblogs_count" => 0,
diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
@@ -9,6 +9,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
only: [try_render: 3, add_link_headers: 2]
require Ecto.Query
+ require Pleroma.Constants
alias Pleroma.Activity
alias Pleroma.Bookmark
@@ -41,7 +42,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
:show,
:context,
:show_history,
- :show_source
+ :show_source,
+ :quotes
]
)
@@ -629,6 +631,45 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
)
end
+ @doc "GET /api/v1/statuses/:id/quotes"
+ def quotes(
+ %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id} = params}}} =
+ conn,
+ _
+ ) do
+ with %Activity{object: object} = activity <- Activity.get_by_id_with_object(id),
+ true <- Visibility.visible_for_user?(activity, user) do
+ params =
+ params
+ |> Map.put(:type, "Create")
+ |> Map.put(:blocking_user, user)
+ |> Map.put(:quote_url, object.data["id"])
+
+ recipients =
+ if user do
+ [Pleroma.Constants.as_public()] ++ [user.ap_id | User.following(user)]
+ else
+ [Pleroma.Constants.as_public()]
+ end
+
+ activities =
+ recipients
+ |> ActivityPub.fetch_activities(params)
+ |> Enum.reverse()
+
+ conn
+ |> add_link_headers(activities)
+ |> render("index.json",
+ activities: activities,
+ for: user,
+ as: :activity
+ )
+ else
+ nil -> {:error, :not_found}
+ false -> {:error, :not_found}
+ end
+ end
+
defp put_application(params, %{assigns: %{token: %Token{user: %User{} = user} = token}} = _conn) do
if user.disclose_client do
%{client_name: client_name, website: website} = Repo.preload(token, :app).app
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -447,6 +447,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
application: build_application(object.data["generator"]),
language: get_language(object),
emojis: build_emojis(object.data["emoji"]),
+ quotes_count: object.data["quotesCount"] || 0,
pleroma: %{
local: activity.local,
conversation_id: get_context_id(activity),
diff --git a/lib/pleroma/web/pleroma_api/controllers/status_controller.ex b/lib/pleroma/web/pleroma_api/controllers/status_controller.ex
@@ -5,16 +5,9 @@
defmodule Pleroma.Web.PleromaAPI.StatusController do
use Pleroma.Web, :controller
- import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
-
require Ecto.Query
require Pleroma.Constants
- alias Pleroma.Activity
- alias Pleroma.User
- alias Pleroma.Web.ActivityPub.ActivityPub
- alias Pleroma.Web.ActivityPub.Visibility
- alias Pleroma.Web.MastodonAPI.StatusView
alias Pleroma.Web.Plugs.OAuthScopesPlug
plug(Pleroma.Web.ApiSpec.CastAndValidate)
@@ -29,38 +22,9 @@ defmodule Pleroma.Web.PleromaAPI.StatusController do
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaStatusOperation
@doc "GET /api/v1/pleroma/statuses/:id/quotes"
- def quotes(%{assigns: %{user: user}} = conn, %{id: id} = params) do
- with %Activity{object: object} = activity <- Activity.get_by_id_with_object(id),
- true <- Visibility.visible_for_user?(activity, user) do
- params =
- params
- |> Map.put(:type, "Create")
- |> Map.put(:blocking_user, user)
- |> Map.put(:quote_url, object.data["id"])
-
- recipients =
- if user do
- [Pleroma.Constants.as_public()] ++ [user.ap_id | User.following(user)]
- else
- [Pleroma.Constants.as_public()]
- end
-
- activities =
- recipients
- |> ActivityPub.fetch_activities(params)
- |> Enum.reverse()
-
- conn
- |> add_link_headers(activities)
- |> put_view(StatusView)
- |> render("index.json",
- activities: activities,
- for: user,
- as: :activity
- )
- else
- nil -> {:error, :not_found}
- false -> {:error, :not_found}
- end
+ def quotes(conn, _params) do
+ conn
+ |> put_view(Pleroma.Web.MastodonAPI.StatusView)
+ |> Pleroma.Web.MastodonAPI.StatusController.call(:quotes)
end
end
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
@@ -752,6 +752,7 @@ defmodule Pleroma.Web.Router do
post("/statuses/:id/mute", StatusController, :mute_conversation)
post("/statuses/:id/unmute", StatusController, :unmute_conversation)
post("/statuses/:id/translate", StatusController, :translate)
+ get("/statuses/:id/quotes", StatusController, :quotes)
post("/push/subscription", SubscriptionController, :create)
get("/push/subscription", SubscriptionController, :show)
diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
@@ -2541,4 +2541,47 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|> json_response_and_validate_schema(404)
end
end
+
+ describe "getting quotes of a specified post" do
+ setup do
+ [current_user, user] = insert_pair(:user)
+ %{user: current_user, conn: conn} = oauth_access(["read:statuses"], user: current_user)
+ [current_user: current_user, user: user, conn: conn]
+ end
+
+ test "shows quotes of a post", %{conn: conn} do
+ user = insert(:user)
+ activity = insert(:note_activity)
+
+ {:ok, quote_post} = CommonAPI.post(user, %{status: "quoat", quote_id: activity.id})
+
+ response =
+ conn
+ |> get("/api/v1/statuses/#{activity.id}/quotes")
+ |> json_response_and_validate_schema(:ok)
+
+ [status] = response
+
+ assert length(response) == 1
+ assert status["id"] == quote_post.id
+ end
+
+ test "returns 404 error when a post can't be seen", %{conn: conn} do
+ activity = insert(:direct_note_activity)
+
+ response =
+ conn
+ |> get("/api/v1/statuses/#{activity.id}/quotes")
+
+ assert json_response_and_validate_schema(response, 404) == %{"error" => "Record not found"}
+ end
+
+ test "returns 404 error when a post does not exist", %{conn: conn} do
+ response =
+ conn
+ |> get("/api/v1/statuses/idontexist/quotes")
+
+ assert json_response_and_validate_schema(response, 404) == %{"error" => "Record not found"}
+ end
+ end
end
diff --git a/test/pleroma/web/mastodon_api/views/status_view_test.exs b/test/pleroma/web/mastodon_api/views/status_view_test.exs
@@ -344,7 +344,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
quotes_count: 0,
bookmark_folder: nil,
list_id: nil
- }
+ },
+ quotes_count: 0
}
assert status == expected
diff --git a/test/pleroma/web/pleroma_api/controllers/status_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/status_controller_test.exs
@@ -9,46 +9,22 @@ defmodule Pleroma.Web.PleromaAPI.StatusControllerTest do
import Pleroma.Factory
- describe "getting quotes of a specified post" do
- setup do
- [current_user, user] = insert_pair(:user)
- %{user: current_user, conn: conn} = oauth_access(["read:statuses"], user: current_user)
- [current_user: current_user, user: user, conn: conn]
- end
+ test "/quotes fallback works" do
+ [current_user, user] = insert_pair(:user)
+ %{conn: conn} = oauth_access(["read:statuses"], user: current_user)
- test "shows quotes of a post", %{conn: conn} do
- user = insert(:user)
- activity = insert(:note_activity)
+ activity = insert(:note_activity)
- {:ok, quote_post} = CommonAPI.post(user, %{status: "quoat", quote_id: activity.id})
+ {:ok, quote_post} = CommonAPI.post(user, %{status: "quoat", quote_id: activity.id})
- response =
- conn
- |> get("/api/v1/pleroma/statuses/#{activity.id}/quotes")
- |> json_response_and_validate_schema(:ok)
+ response =
+ conn
+ |> get("/api/v1/pleroma/statuses/#{activity.id}/quotes")
+ |> json_response_and_validate_schema(:ok)
- [status] = response
+ [status] = response
- assert length(response) == 1
- assert status["id"] == quote_post.id
- end
-
- test "returns 404 error when a post can't be seen", %{conn: conn} do
- activity = insert(:direct_note_activity)
-
- response =
- conn
- |> get("/api/v1/pleroma/statuses/#{activity.id}/quotes")
-
- assert json_response_and_validate_schema(response, 404) == %{"error" => "Record not found"}
- end
-
- test "returns 404 error when a post does not exist", %{conn: conn} do
- response =
- conn
- |> get("/api/v1/pleroma/statuses/idontexist/quotes")
-
- assert json_response_and_validate_schema(response, 404) == %{"error" => "Record not found"}
- end
+ assert length(response) == 1
+ assert status["id"] == quote_post.id
end
end