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 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 card_operation do
332 tags: ["Retrieve status information"],
334 summary: "Preview card",
335 description: "Deprecated in favor of card property inlined on Status entity",
336 operationId: "StatusController.card",
337 parameters: [id_param()],
338 security: [%{"oAuth" => ["read:statuses"]}],
341 Operation.response("Card", "application/json", %Schema{
345 type: %Schema{type: :string, enum: ["link", "photo", "video", "rich"]},
346 provider_name: %Schema{type: :string, nullable: true},
347 provider_url: %Schema{type: :string, format: :uri},
348 url: %Schema{type: :string, format: :uri},
349 image: %Schema{type: :string, nullable: true, format: :uri},
350 title: %Schema{type: :string},
351 description: %Schema{type: :string}
358 def favourited_by_operation do
360 tags: ["Retrieve status information"],
361 summary: "Favourited by",
362 description: "View who favourited a given status",
363 operationId: "StatusController.favourited_by",
364 security: [%{"oAuth" => ["read:accounts"]}],
365 parameters: [id_param()],
371 AccountOperation.array_of_accounts()
373 404 => Operation.response("Not Found", "application/json", ApiError)
378 def reblogged_by_operation do
380 tags: ["Retrieve status information"],
381 summary: "Reblogged by",
382 description: "View who reblogged a given status",
383 operationId: "StatusController.reblogged_by",
384 security: [%{"oAuth" => ["read:accounts"]}],
385 parameters: [id_param()],
391 AccountOperation.array_of_accounts()
393 404 => Operation.response("Not Found", "application/json", ApiError)
398 def context_operation do
400 tags: ["Retrieve status information"],
401 summary: "Parent and child statuses",
402 description: "View statuses above and below this status in the thread",
403 operationId: "StatusController.context",
404 security: [%{"oAuth" => ["read:statuses"]}],
405 parameters: [id_param()],
407 200 => Operation.response("Context", "application/json", context())
412 def favourites_operation do
415 summary: "Favourited statuses",
417 "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.",
418 operationId: "StatusController.favourites",
419 parameters: pagination_params(),
420 security: [%{"oAuth" => ["read:favourites"]}],
422 200 => Operation.response("Array of Statuses", "application/json", array_of_statuses())
427 def bookmarks_operation do
430 summary: "Bookmarked statuses",
431 description: "Statuses the user has bookmarked",
432 operationId: "StatusController.bookmarks",
433 parameters: pagination_params(),
434 security: [%{"oAuth" => ["read:bookmarks"]}],
436 200 => Operation.response("Array of Statuses", "application/json", array_of_statuses())
441 def show_history_operation do
443 tags: ["Retrieve status history"],
444 summary: "Status history",
445 description: "View history of a status",
446 operationId: "StatusController.show_history",
447 security: [%{"oAuth" => ["read:statuses"]}],
452 200 => status_history_response(),
453 404 => Operation.response("Not Found", "application/json", ApiError)
458 def show_source_operation do
460 tags: ["Retrieve status source"],
461 summary: "Status source",
462 description: "View source of a status",
463 operationId: "StatusController.show_source",
464 security: [%{"oAuth" => ["read:statuses"]}],
469 200 => status_source_response(),
470 404 => Operation.response("Not Found", "application/json", ApiError)
475 def update_operation do
477 tags: ["Update status"],
478 summary: "Update status",
479 description: "Change the content of a status",
480 operationId: "StatusController.update",
481 security: [%{"oAuth" => ["write:statuses"]}],
485 requestBody: request_body("Parameters", update_request(), required: true),
487 200 => status_response(),
488 403 => Operation.response("Forbidden", "application/json", ApiError),
489 404 => Operation.response("Not Found", "application/json", ApiError)
494 def array_of_statuses do
495 %Schema{type: :array, items: Status, example: [Status.schema().example]}
498 defp create_request do
500 title: "StatusCreateRequest",
507 "Text content of the status. If `media_ids` is provided, this becomes optional. Attaching a `poll` is optional while `status` is provided."
512 items: %Schema{type: :string},
513 description: "Array of Attachment ids to be attached as media."
516 in_reply_to_id: %Schema{
519 description: "ID of the status being replied to, if status is a reply"
522 allOf: [BooleanLike],
524 description: "Mark status and attached media as sensitive?"
526 spoiler_text: %Schema{
530 "Text to be shown as a warning or subject before the actual content. Statuses are generally collapsed behind this field."
532 scheduled_at: %Schema{
534 format: :"date-time",
537 "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."
542 description: "ISO 639 language code for this status."
544 # Pleroma-specific properties:
546 allOf: [BooleanLike],
549 "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"
551 content_type: %Schema{
555 "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."
560 items: %Schema{type: :string},
562 "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"
568 %Schema{type: :string, description: "`list:LIST_ID`", example: "LIST:123"}
571 "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`"
577 "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."
579 in_reply_to_conversation_id: %Schema{
583 "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`."
587 "status" => "What time is it?",
588 "sensitive" => "false",
590 "options" => ["Cofe", "Adventure"],
597 defp update_request do
599 title: "StatusUpdateRequest",
606 "Text content of the status. If `media_ids` is provided, this becomes optional. Attaching a `poll` is optional while `status` is provided."
611 items: %Schema{type: :string},
612 description: "Array of Attachment ids to be attached as media."
616 allOf: [BooleanLike],
618 description: "Mark status and attached media as sensitive?"
620 spoiler_text: %Schema{
624 "Text to be shown as a warning or subject before the actual content. Statuses are generally collapsed behind this field."
626 content_type: %Schema{
630 "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."
635 items: %Schema{type: :string},
637 "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"
641 "status" => "What time is it?",
642 "sensitive" => "false",
644 "options" => ["Cofe", "Adventure"],
655 required: [:options, :expires_in],
659 items: %Schema{type: :string},
660 description: "Array of possible answers. Must be provided with `poll[expires_in]`."
666 "Duration the poll should be open, in seconds. Must be provided with `poll[options]`"
669 allOf: [BooleanLike],
671 description: "Allow multiple choices?"
673 hide_totals: %Schema{
674 allOf: [BooleanLike],
676 description: "Hide vote counts until the poll ends?"
683 Operation.parameter(:id, :path, FlakeID, "Status ID",
684 example: "9umDrYheeY451cQnEe",
689 defp status_response do
690 Operation.response("Status", "application/json", Status)
693 defp status_history_response do
698 title: "Status history",
699 description: "Response schema for history of a status",
706 description: "The account that authored this status"
711 description: "HTML-encoded status content"
715 description: "Is this status marked as sensitive content?"
717 spoiler_text: %Schema{
720 "Subject or summary line, below which status content is collapsed until expanded"
725 description: "The date when this status was created"
727 media_attachments: %Schema{
730 description: "Media that is attached to this status"
735 description: "Custom emoji to be used when rendering status content"
740 description: "The poll attached to the status"
748 defp status_source_response do
758 description: "Raw source of status content"
760 spoiler_text: %Schema{
763 "Subject or summary line, below which status content is collapsed until expanded"
765 content_type: %Schema{
767 description: "The content type of the source"
776 title: "StatusContext",
778 "Represents the tree around a given status. Used for reconstructing threads of statuses.",
780 required: [:ancestors, :descendants],
782 ancestors: array_of_statuses(),
783 descendants: array_of_statuses()
786 "ancestors" => [Status.schema().example],
787 "descendants" => [Status.schema().example]