1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2021 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 200 => status_response()
265 def unbookmark_operation do
267 tags: ["Status actions"],
268 summary: "Undo bookmark",
269 security: [%{"oAuth" => ["write:bookmarks"]}],
270 description: "Remove a status from your private bookmarks",
271 operationId: "StatusController.unbookmark",
272 parameters: [id_param()],
274 200 => status_response()
279 def mute_conversation_operation do
281 tags: ["Status actions"],
282 summary: "Mute conversation",
283 security: [%{"oAuth" => ["write:mutes"]}],
284 description: "Do not receive notifications for the thread that this status is part of.",
285 operationId: "StatusController.mute_conversation",
287 request_body("Parameters", %Schema{
293 description: "Expire the mute in `expires_in` seconds. Default 0 for infinity",
303 %Schema{type: :integer, default: 0},
304 "Expire the mute in `expires_in` seconds. Default 0 for infinity"
308 200 => status_response(),
309 400 => Operation.response("Error", "application/json", ApiError)
314 def unmute_conversation_operation do
316 tags: ["Status actions"],
317 summary: "Unmute conversation",
318 security: [%{"oAuth" => ["write:mutes"]}],
320 "Start receiving notifications again for the thread that this status is part of",
321 operationId: "StatusController.unmute_conversation",
322 parameters: [id_param()],
324 200 => status_response(),
325 400 => Operation.response("Error", "application/json", ApiError)
330 def favourited_by_operation do
332 tags: ["Retrieve status information"],
333 summary: "Favourited by",
334 description: "View who favourited a given status",
335 operationId: "StatusController.favourited_by",
336 security: [%{"oAuth" => ["read:accounts"]}],
337 parameters: [id_param()],
343 AccountOperation.array_of_accounts()
345 404 => Operation.response("Not Found", "application/json", ApiError)
350 def reblogged_by_operation do
352 tags: ["Retrieve status information"],
353 summary: "Reblogged by",
354 description: "View who reblogged a given status",
355 operationId: "StatusController.reblogged_by",
356 security: [%{"oAuth" => ["read:accounts"]}],
357 parameters: [id_param()],
363 AccountOperation.array_of_accounts()
365 404 => Operation.response("Not Found", "application/json", ApiError)
370 def context_operation do
372 tags: ["Retrieve status information"],
373 summary: "Parent and child statuses",
374 description: "View statuses above and below this status in the thread",
375 operationId: "StatusController.context",
376 security: [%{"oAuth" => ["read:statuses"]}],
377 parameters: [id_param()],
379 200 => Operation.response("Context", "application/json", context())
384 def favourites_operation do
387 summary: "Favourited statuses",
389 "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.",
390 operationId: "StatusController.favourites",
391 parameters: pagination_params(),
392 security: [%{"oAuth" => ["read:favourites"]}],
394 200 => Operation.response("Array of Statuses", "application/json", array_of_statuses())
399 def bookmarks_operation do
402 summary: "Bookmarked statuses",
403 description: "Statuses the user has bookmarked",
404 operationId: "StatusController.bookmarks",
405 parameters: pagination_params(),
406 security: [%{"oAuth" => ["read:bookmarks"]}],
408 200 => Operation.response("Array of Statuses", "application/json", array_of_statuses())
413 def translate_operation do
415 tags: ["Retrieve status translation"],
416 summary: "Translate status",
417 description: "View the translation of a given status",
418 operationId: "StatusController.translation",
419 security: [%{"oAuth" => ["read:statuses"]}],
420 parameters: [id_param(), language_param(), source_language_param()],
422 200 => Operation.response("Translation", "application/json", translation()),
423 400 => Operation.response("Error", "application/json", ApiError),
424 404 => Operation.response("Not Found", "application/json", ApiError)
429 def show_history_operation do
431 tags: ["Retrieve status history"],
432 summary: "Status history",
433 description: "View history of a status",
434 operationId: "StatusController.show_history",
435 security: [%{"oAuth" => ["read:statuses"]}],
440 200 => status_history_response(),
441 404 => Operation.response("Not Found", "application/json", ApiError)
446 def show_source_operation do
448 tags: ["Retrieve status source"],
449 summary: "Status source",
450 description: "View source of a status",
451 operationId: "StatusController.show_source",
452 security: [%{"oAuth" => ["read:statuses"]}],
457 200 => status_source_response(),
458 404 => Operation.response("Not Found", "application/json", ApiError)
463 def update_operation do
465 tags: ["Update status"],
466 summary: "Update status",
467 description: "Change the content of a status",
468 operationId: "StatusController.update",
469 security: [%{"oAuth" => ["write:statuses"]}],
473 requestBody: request_body("Parameters", update_request(), required: true),
475 200 => status_response(),
476 403 => Operation.response("Forbidden", "application/json", ApiError),
477 404 => Operation.response("Not Found", "application/json", ApiError)
482 def array_of_statuses do
483 %Schema{type: :array, items: Status, example: [Status.schema().example]}
486 defp create_request do
488 title: "StatusCreateRequest",
495 "Text content of the status. If `media_ids` is provided, this becomes optional. Attaching a `poll` is optional while `status` is provided."
500 items: %Schema{type: :string},
501 description: "Array of Attachment ids to be attached as media."
504 in_reply_to_id: %Schema{
507 description: "ID of the status being replied to, if status is a reply"
510 allOf: [BooleanLike],
512 description: "Mark status and attached media as sensitive?"
514 spoiler_text: %Schema{
518 "Text to be shown as a warning or subject before the actual content. Statuses are generally collapsed behind this field."
520 scheduled_at: %Schema{
522 format: :"date-time",
525 "ISO 8601 Datetime at which to schedule a status. Providing this paramter will cause ScheduledStatus to be returned instead of Status. Must be at least 5 minutes in the future."
530 description: "ISO 639 language code for this status."
532 # Pleroma-specific properties:
534 allOf: [BooleanLike],
537 "If set to `true` the post won't be actually posted, but the status entitiy would still be rendered back. This could be useful for previewing rich text/custom emoji, for example"
539 content_type: %Schema{
543 "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."
548 items: %Schema{type: :string},
550 "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"
556 %Schema{type: :string, description: "`list:LIST_ID`", example: "LIST:123"}
559 "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`"
565 "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."
567 in_reply_to_conversation_id: %Schema{
571 "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`."
576 description: "Will quote a given status."
580 "status" => "What time is it?",
581 "sensitive" => "false",
583 "options" => ["Cofe", "Adventure"],
590 defp update_request do
592 title: "StatusUpdateRequest",
599 "Text content of the status. If `media_ids` is provided, this becomes optional. Attaching a `poll` is optional while `status` is provided."
604 items: %Schema{type: :string},
605 description: "Array of Attachment ids to be attached as media."
609 allOf: [BooleanLike],
611 description: "Mark status and attached media as sensitive?"
613 spoiler_text: %Schema{
617 "Text to be shown as a warning or subject before the actual content. Statuses are generally collapsed behind this field."
619 content_type: %Schema{
623 "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."
628 items: %Schema{type: :string},
630 "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"
634 "status" => "What time is it?",
635 "sensitive" => "false",
637 "options" => ["Cofe", "Adventure"],
648 required: [:options, :expires_in],
652 items: %Schema{type: :string},
653 description: "Array of possible answers. Must be provided with `poll[expires_in]`."
659 "Duration the poll should be open, in seconds. Must be provided with `poll[options]`"
662 allOf: [BooleanLike],
664 description: "Allow multiple choices?"
666 hide_totals: %Schema{
667 allOf: [BooleanLike],
669 description: "Hide vote counts until the poll ends?"
676 Operation.parameter(:id, :path, FlakeID, "Status ID",
677 example: "9umDrYheeY451cQnEe",
682 defp language_param do
683 Operation.parameter(:language, :path, :string, "ISO 639 language code", example: "en")
686 defp source_language_param do
687 Operation.parameter(:from, :query, :string, "ISO 639 language code", example: "en")
690 defp status_response do
691 Operation.response("Status", "application/json", Status)
694 defp status_history_response do
699 title: "Status history",
700 description: "Response schema for history of a status",
707 description: "The account that authored this status"
712 description: "HTML-encoded status content"
716 description: "Is this status marked as sensitive content?"
718 spoiler_text: %Schema{
721 "Subject or summary line, below which status content is collapsed until expanded"
726 description: "The date when this status was created"
728 media_attachments: %Schema{
731 description: "Media that is attached to this status"
736 description: "Custom emoji to be used when rendering status content"
741 description: "The poll attached to the status"
749 defp status_source_response do
759 description: "Raw source of status content"
761 spoiler_text: %Schema{
764 "Subject or summary line, below which status content is collapsed until expanded"
766 content_type: %Schema{
768 description: "The content type of the source"
777 title: "StatusContext",
779 "Represents the tree around a given status. Used for reconstructing threads of statuses.",
781 required: [:ancestors, :descendants],
783 ancestors: array_of_statuses(),
784 descendants: array_of_statuses()
787 "ancestors" => [Status.schema().example],
788 "descendants" => [Status.schema().example]
795 title: "StatusTranslation",
796 description: "The translation of a status.",
798 required: [:detected_language, :text],
800 detected_language: %Schema{
802 description: "The detected language of the text"
804 text: %Schema{type: :string, description: "The translated text"}