1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.Web.ApiSpec.StatusOperation do
6 alias OpenApiSpex.Operation
7 alias OpenApiSpex.Schema
8 alias Pleroma.Web.ApiSpec.AccountOperation
9 alias Pleroma.Web.ApiSpec.Schemas.Account
10 alias Pleroma.Web.ApiSpec.Schemas.ApiError
11 alias Pleroma.Web.ApiSpec.Schemas.Attachment
12 alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
13 alias Pleroma.Web.ApiSpec.Schemas.Emoji
14 alias Pleroma.Web.ApiSpec.Schemas.FlakeID
15 alias Pleroma.Web.ApiSpec.Schemas.Poll
16 alias Pleroma.Web.ApiSpec.Schemas.ScheduledStatus
17 alias Pleroma.Web.ApiSpec.Schemas.Status
18 alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
20 import Pleroma.Web.ApiSpec.Helpers
22 def open_api_operation(action) do
23 operation = String.to_existing_atom("#{action}_operation")
24 apply(__MODULE__, operation, [])
27 def index_operation do
29 tags: ["Retrieve status information"],
30 summary: "Multiple statuses",
31 security: [%{"oAuth" => ["read:statuses"]}],
36 %Schema{type: :array, items: FlakeID},
43 "Include reactions from muted acccounts."
46 operationId: "StatusController.index",
48 200 => Operation.response("Array of Status", "application/json", array_of_statuses())
53 def create_operation do
55 tags: ["Status actions"],
56 summary: "Publish new status",
57 security: [%{"oAuth" => ["write:statuses"]}],
58 description: "Post a new status",
59 operationId: "StatusController.create",
60 requestBody: request_body("Parameters", create_request(), required: true),
64 "Status. When `scheduled_at` is present, ScheduledStatus is returned instead",
66 %Schema{anyOf: [Status, ScheduledStatus]}
68 422 => Operation.response("Bad Request / MRF Rejection", "application/json", ApiError)
75 tags: ["Retrieve status information"],
77 description: "View information about a status",
78 operationId: "StatusController.show",
79 security: [%{"oAuth" => ["read:statuses"]}],
86 "Include reactions from muted acccounts."
90 200 => status_response(),
91 404 => Operation.response("Not Found", "application/json", ApiError)
96 def delete_operation do
98 tags: ["Status actions"],
100 security: [%{"oAuth" => ["write:statuses"]}],
101 description: "Delete one of your own statuses",
102 operationId: "StatusController.delete",
103 parameters: [id_param()],
105 200 => status_response(),
106 403 => Operation.response("Forbidden", "application/json", ApiError),
107 404 => Operation.response("Not Found", "application/json", ApiError)
112 def reblog_operation do
114 tags: ["Status actions"],
116 security: [%{"oAuth" => ["write:statuses"]}],
117 description: "Share a status",
118 operationId: "StatusController.reblog",
119 parameters: [id_param()],
121 request_body("Parameters", %Schema{
124 visibility: %Schema{allOf: [VisibilityScope]}
128 200 => status_response(),
129 404 => Operation.response("Not Found", "application/json", ApiError)
134 def unreblog_operation do
136 tags: ["Status actions"],
137 summary: "Undo reblog",
138 security: [%{"oAuth" => ["write:statuses"]}],
139 description: "Undo a reshare of a status",
140 operationId: "StatusController.unreblog",
141 parameters: [id_param()],
143 200 => status_response(),
144 404 => Operation.response("Not Found", "application/json", ApiError)
149 def favourite_operation do
151 tags: ["Status actions"],
152 summary: "Favourite",
153 security: [%{"oAuth" => ["write:favourites"]}],
154 description: "Add a status to your favourites list",
155 operationId: "StatusController.favourite",
156 parameters: [id_param()],
158 200 => status_response(),
159 404 => Operation.response("Not Found", "application/json", ApiError)
164 def unfavourite_operation do
166 tags: ["Status actions"],
167 summary: "Undo favourite",
168 security: [%{"oAuth" => ["write:favourites"]}],
169 description: "Remove a status from your favourites list",
170 operationId: "StatusController.unfavourite",
171 parameters: [id_param()],
173 200 => status_response(),
174 404 => Operation.response("Not Found", "application/json", ApiError)
181 tags: ["Status actions"],
182 summary: "Pin to profile",
183 security: [%{"oAuth" => ["write:accounts"]}],
184 description: "Feature one of your own public statuses at the top of your profile",
185 operationId: "StatusController.pin",
186 parameters: [id_param()],
188 200 => status_response(),
190 Operation.response("Bad Request", "application/json", %Schema{
192 title: "Unprocessable Entity",
194 "error" => "You have already pinned the maximum number of statuses"
198 Operation.response("Not found", "application/json", %Schema{
200 title: "Unprocessable Entity",
202 "error" => "Record not found"
207 "Unprocessable Entity",
211 title: "Unprocessable Entity",
213 "error" => "Someone else's status cannot be pinned"
221 def unpin_operation do
223 tags: ["Status actions"],
224 summary: "Unpin from profile",
225 security: [%{"oAuth" => ["write:accounts"]}],
226 description: "Unfeature a status from the top of your profile",
227 operationId: "StatusController.unpin",
228 parameters: [id_param()],
230 200 => status_response(),
232 Operation.response("Bad Request", "application/json", %Schema{
234 title: "Unprocessable Entity",
236 "error" => "You have already pinned the maximum number of statuses"
240 Operation.response("Not found", "application/json", %Schema{
242 title: "Unprocessable Entity",
244 "error" => "Record not found"
251 def bookmark_operation do
253 tags: ["Status actions"],
255 security: [%{"oAuth" => ["write:bookmarks"]}],
256 description: "Privately bookmark a status",
257 operationId: "StatusController.bookmark",
258 parameters: [id_param()],
260 request_body("Parameters", %Schema{
261 title: "StatusUpdateRequest",
267 description: "ID of bookmarks folder, if any"
272 200 => status_response()
277 def unbookmark_operation do
279 tags: ["Status actions"],
280 summary: "Undo bookmark",
281 security: [%{"oAuth" => ["write:bookmarks"]}],
282 description: "Remove a status from your private bookmarks",
283 operationId: "StatusController.unbookmark",
284 parameters: [id_param()],
286 200 => status_response()
291 def mute_conversation_operation do
293 tags: ["Status actions"],
294 summary: "Mute conversation",
295 security: [%{"oAuth" => ["write:mutes"]}],
296 description: "Do not receive notifications for the thread that this status is part of.",
297 operationId: "StatusController.mute_conversation",
299 request_body("Parameters", %Schema{
305 description: "Expire the mute in `expires_in` seconds. Default 0 for infinity",
315 %Schema{type: :integer, default: 0},
316 "Expire the mute in `expires_in` seconds. Default 0 for infinity"
320 200 => status_response(),
321 400 => Operation.response("Error", "application/json", ApiError)
326 def unmute_conversation_operation do
328 tags: ["Status actions"],
329 summary: "Unmute conversation",
330 security: [%{"oAuth" => ["write:mutes"]}],
332 "Start receiving notifications again for the thread that this status is part of",
333 operationId: "StatusController.unmute_conversation",
334 parameters: [id_param()],
336 200 => status_response(),
337 400 => Operation.response("Error", "application/json", ApiError)
342 def card_operation do
344 tags: ["Retrieve status information"],
346 summary: "Preview card",
347 description: "Deprecated in favor of card property inlined on Status entity",
348 operationId: "StatusController.card",
349 parameters: [id_param()],
350 security: [%{"oAuth" => ["read:statuses"]}],
353 Operation.response("Card", "application/json", %Schema{
357 type: %Schema{type: :string, enum: ["link", "photo", "video", "rich"]},
358 provider_name: %Schema{type: :string, nullable: true},
359 provider_url: %Schema{type: :string, format: :uri},
360 url: %Schema{type: :string, format: :uri},
361 image: %Schema{type: :string, nullable: true, format: :uri},
362 title: %Schema{type: :string},
363 description: %Schema{type: :string}
370 def favourited_by_operation do
372 tags: ["Retrieve status information"],
373 summary: "Favourited by",
374 description: "View who favourited a given status",
375 operationId: "StatusController.favourited_by",
376 security: [%{"oAuth" => ["read:accounts"]}],
377 parameters: [id_param()],
383 AccountOperation.array_of_accounts()
385 404 => Operation.response("Not Found", "application/json", ApiError)
390 def reblogged_by_operation do
392 tags: ["Retrieve status information"],
393 summary: "Reblogged by",
394 description: "View who reblogged a given status",
395 operationId: "StatusController.reblogged_by",
396 security: [%{"oAuth" => ["read:accounts"]}],
397 parameters: [id_param()],
403 AccountOperation.array_of_accounts()
405 404 => Operation.response("Not Found", "application/json", ApiError)
410 def context_operation do
412 tags: ["Retrieve status information"],
413 summary: "Parent and child statuses",
414 description: "View statuses above and below this status in the thread",
415 operationId: "StatusController.context",
416 security: [%{"oAuth" => ["read:statuses"]}],
417 parameters: [id_param()],
419 200 => Operation.response("Context", "application/json", context())
424 def favourites_operation do
427 summary: "Favourited statuses",
429 "Statuses the user has favourited. Please note that you have to use the link headers to paginate this. You can not build the query parameters yourself.",
430 operationId: "StatusController.favourites",
431 parameters: pagination_params(),
432 security: [%{"oAuth" => ["read:favourites"]}],
434 200 => Operation.response("Array of Statuses", "application/json", array_of_statuses())
439 def bookmarks_operation do
442 summary: "Bookmarked statuses",
443 description: "Statuses the user has bookmarked",
444 operationId: "StatusController.bookmarks",
450 "If provided, only display bookmarks from given folder"
452 | pagination_params()
454 security: [%{"oAuth" => ["read:bookmarks"]}],
456 200 => Operation.response("Array of Statuses", "application/json", array_of_statuses())
461 def show_history_operation do
463 tags: ["Retrieve status information"],
464 summary: "Status history",
465 description: "View history of a status",
466 operationId: "StatusController.show_history",
467 security: [%{"oAuth" => ["read:statuses"]}],
472 200 => status_history_response(),
473 404 => Operation.response("Not Found", "application/json", ApiError)
478 def show_source_operation do
480 tags: ["Retrieve status information"],
481 summary: "Status source",
482 description: "View source of a status",
483 operationId: "StatusController.show_source",
484 security: [%{"oAuth" => ["read:statuses"]}],
489 200 => status_source_response(),
490 404 => Operation.response("Not Found", "application/json", ApiError)
495 def update_operation do
497 tags: ["Status actions"],
498 summary: "Update status",
499 description: "Change the content of a status",
500 operationId: "StatusController.update",
501 security: [%{"oAuth" => ["write:statuses"]}],
505 requestBody: request_body("Parameters", update_request(), required: true),
507 200 => status_response(),
508 403 => Operation.response("Forbidden", "application/json", ApiError),
509 404 => Operation.response("Not Found", "application/json", ApiError)
514 def array_of_statuses do
515 %Schema{type: :array, items: Status, example: [Status.schema().example]}
518 defp create_request do
520 title: "StatusCreateRequest",
527 "Text content of the status. If `media_ids` is provided, this becomes optional. Attaching a `poll` is optional while `status` is provided."
532 items: %Schema{type: :string},
533 description: "Array of Attachment ids to be attached as media."
536 in_reply_to_id: %Schema{
539 description: "ID of the status being replied to, if status is a reply"
542 allOf: [BooleanLike],
544 description: "Mark status and attached media as sensitive?"
546 spoiler_text: %Schema{
550 "Text to be shown as a warning or subject before the actual content. Statuses are generally collapsed behind this field."
552 scheduled_at: %Schema{
554 format: :"date-time",
557 "ISO 8601 Datetime at which to schedule a status. Providing this parameter will cause ScheduledStatus to be returned instead of Status. Must be at least 5 minutes in the future."
562 description: "ISO 639 language code for this status."
564 # Pleroma-specific properties:
566 allOf: [BooleanLike],
569 "If set to `true` the post won't be actually posted, but the status entity would still be rendered back. This could be useful for previewing rich text/custom emoji, for example"
571 content_type: %Schema{
575 "The MIME type of the status, it is transformed into HTML by the backend. You can get the list of the supported MIME types with the nodeinfo endpoint."
580 items: %Schema{type: :string},
582 "A list of nicknames (like `lain@soykaf.club` or `lain` on the local server) that will be used to determine who is going to be addressed by this post. Using this will disable the implicit addressing by mentioned names in the `status` body, only the people in the `to` list will be addressed. The normal rules for for post visibility are not affected by this and will still apply"
588 %Schema{type: :string, description: "`list:LIST_ID`", example: "LIST:123"}
591 "Visibility of the posted status. Besides standard MastoAPI values (`direct`, `private`, `unlisted` or `public`) it can be used to address a List by setting it to `list:LIST_ID`"
597 "The number of seconds the posted activity should expire in. When a posted activity expires it will be deleted from the server, and a delete request for it will be federated. This needs to be longer than an hour."
599 in_reply_to_conversation_id: %Schema{
603 "Will reply to a given conversation, addressing only the people who are part of the recipient set of that conversation. Sets the visibility to `direct`."
608 description: "ID of the status being quoted, if any"
612 "status" => "What time is it?",
613 "sensitive" => "false",
615 "options" => ["Cofe", "Adventure"],
622 defp update_request do
624 title: "StatusUpdateRequest",
631 "Text content of the status. If `media_ids` is provided, this becomes optional. Attaching a `poll` is optional while `status` is provided."
636 items: %Schema{type: :string},
637 description: "Array of Attachment ids to be attached as media."
641 allOf: [BooleanLike],
643 description: "Mark status and attached media as sensitive?"
645 spoiler_text: %Schema{
649 "Text to be shown as a warning or subject before the actual content. Statuses are generally collapsed behind this field."
651 content_type: %Schema{
655 "The MIME type of the status, it is transformed into HTML by the backend. You can get the list of the supported MIME types with the nodeinfo endpoint."
660 items: %Schema{type: :string},
662 "A list of nicknames (like `lain@soykaf.club` or `lain` on the local server) that will be used to determine who is going to be addressed by this post. Using this will disable the implicit addressing by mentioned names in the `status` body, only the people in the `to` list will be addressed. The normal rules for for post visibility are not affected by this and will still apply"
666 "status" => "What time is it?",
667 "sensitive" => "false",
669 "options" => ["Cofe", "Adventure"],
680 required: [:options, :expires_in],
684 items: %Schema{type: :string},
685 description: "Array of possible answers. Must be provided with `poll[expires_in]`."
691 "Duration the poll should be open, in seconds. Must be provided with `poll[options]`"
694 allOf: [BooleanLike],
696 description: "Allow multiple choices?"
698 hide_totals: %Schema{
699 allOf: [BooleanLike],
701 description: "Hide vote counts until the poll ends?"
708 Operation.parameter(:id, :path, FlakeID.schema(), "Status ID",
709 example: "9umDrYheeY451cQnEe",
714 defp status_response do
715 Operation.response("Status", "application/json", Status)
718 defp status_history_response do
723 title: "Status history",
724 description: "Response schema for history of a status",
731 description: "The account that authored this status"
736 description: "HTML-encoded status content"
740 description: "Is this status marked as sensitive content?"
742 spoiler_text: %Schema{
745 "Subject or summary line, below which status content is collapsed until expanded"
750 description: "The date when this status was created"
752 media_attachments: %Schema{
755 description: "Media that is attached to this status"
760 description: "Custom emoji to be used when rendering status content"
765 description: "The poll attached to the status"
773 defp status_source_response do
783 description: "Raw source of status content"
785 spoiler_text: %Schema{
788 "Subject or summary line, below which status content is collapsed until expanded"
790 content_type: %Schema{
792 description: "The content type of the source"
801 title: "StatusContext",
803 "Represents the tree around a given status. Used for reconstructing threads of statuses.",
805 required: [:ancestors, :descendants],
807 ancestors: array_of_statuses(),
808 descendants: array_of_statuses()
811 "ancestors" => [Status.schema().example],
812 "descendants" => [Status.schema().example]