You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

178 lines
6.0 KiB

  1. # frozen_string_literal: true
  2. class Api::V1::StatusesController < Api::BaseController
  3. include Authorization
  4. before_action -> { authorize_if_got_token! :read, :'read:statuses' }, except: [:create, :update, :destroy]
  5. before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, only: [:create, :update, :destroy]
  6. before_action :require_user!, except: [:show, :context]
  7. before_action :set_status, only: [:show, :context]
  8. before_action :set_thread, only: [:create]
  9. override_rate_limit_headers :create, family: :statuses
  10. override_rate_limit_headers :update, family: :statuses
  11. # This API was originally unlimited, pagination cannot be introduced without
  12. # breaking backwards-compatibility. Arbitrarily high number to cover most
  13. # conversations as quasi-unlimited, it would be too much work to render more
  14. # than this anyway
  15. CONTEXT_LIMIT = 4_096
  16. # This remains expensive and we don't want to show everything to logged-out users
  17. ANCESTORS_LIMIT = 40
  18. DESCENDANTS_LIMIT = 60
  19. DESCENDANTS_DEPTH_LIMIT = 20
  20. def show
  21. @status = cache_collection([@status], Status).first
  22. render json: @status, serializer: REST::StatusSerializer
  23. end
  24. def context
  25. ancestors_limit = CONTEXT_LIMIT
  26. descendants_limit = CONTEXT_LIMIT
  27. descendants_depth_limit = nil
  28. if current_account.nil?
  29. ancestors_limit = ANCESTORS_LIMIT
  30. descendants_limit = DESCENDANTS_LIMIT
  31. descendants_depth_limit = DESCENDANTS_DEPTH_LIMIT
  32. end
  33. ancestors_results = @status.in_reply_to_id.nil? ? [] : @status.ancestors(ancestors_limit, current_account)
  34. descendants_results = @status.descendants(descendants_limit, current_account, descendants_depth_limit)
  35. loaded_ancestors = cache_collection(ancestors_results, Status)
  36. loaded_descendants = cache_collection(descendants_results, Status)
  37. @context = Context.new(ancestors: loaded_ancestors, descendants: loaded_descendants)
  38. statuses = [@status] + @context.ancestors + @context.descendants
  39. render json: @context, serializer: REST::ContextSerializer, relationships: StatusRelationshipsPresenter.new(statuses, current_user&.account_id)
  40. end
  41. def generate_anon_name(input, namelist, note)
  42. name = namelist[Digest::SHA2.hexdigest(input).to_i(16) % namelist.size]
  43. name.in?(note) ? nil : name
  44. end
  45. def create
  46. ori_text = status_params[:status]
  47. anon = Rails.configuration.x.anon
  48. anon_name = anon.acc &&
  49. ori_text.strip.end_with?(anon.tag, "#{anon.tag} 👁️") &&
  50. generate_anon_name(
  51. current_user.account.username + anon.salt + 5.hours.ago.strftime('%D'),
  52. anon.namelist,
  53. Account.find(anon.acc).note
  54. )
  55. sender = anon_name ? Account.find(anon.acc) : current_user.account
  56. st_text = anon_name ? "[#{anon_name}]:\n#{ori_text}" : ori_text
  57. @status = PostStatusService.new.call(
  58. sender,
  59. text: st_text,
  60. thread: @thread,
  61. media_ids: status_params[:media_ids],
  62. sensitive: status_params[:sensitive],
  63. spoiler_text: status_params[:spoiler_text],
  64. visibility: status_params[:visibility],
  65. language: status_params[:language],
  66. scheduled_at: status_params[:scheduled_at],
  67. application: doorkeeper_token.application,
  68. poll: status_params[:poll],
  69. content_type: status_params[:content_type],
  70. allowed_mentions: status_params[:allowed_mentions],
  71. idempotency: request.headers['Idempotency-Key'],
  72. with_rate_limit: true
  73. )
  74. render json: @status, serializer: @status.is_a?(ScheduledStatus) ? REST::ScheduledStatusSerializer : REST::StatusSerializer
  75. rescue PostStatusService::UnexpectedMentionsError => e
  76. unexpected_accounts = ActiveModel::Serializer::CollectionSerializer.new(
  77. e.accounts,
  78. serializer: REST::AccountSerializer
  79. )
  80. render json: { error: e.message, unexpected_accounts: unexpected_accounts }, status: 422
  81. end
  82. def update
  83. @status = Status.where(account: current_account).find(params[:id])
  84. authorize @status, :update?
  85. UpdateStatusService.new.call(
  86. @status,
  87. current_account.id,
  88. text: status_params[:status],
  89. media_ids: status_params[:media_ids],
  90. media_attributes: status_params[:media_attributes],
  91. sensitive: status_params[:sensitive],
  92. language: status_params[:language],
  93. spoiler_text: status_params[:spoiler_text],
  94. poll: status_params[:poll],
  95. content_type: status_params[:content_type]
  96. )
  97. render json: @status, serializer: REST::StatusSerializer
  98. end
  99. def destroy
  100. @status = Status.where(account: current_account).find(params[:id])
  101. authorize @status, :destroy?
  102. @status.discard_with_reblogs
  103. StatusPin.find_by(status: @status)&.destroy
  104. @status.account.statuses_count = @status.account.statuses_count - 1
  105. json = render_to_body json: @status, serializer: REST::StatusSerializer, source_requested: true
  106. RemovalWorker.perform_async(@status.id, { 'redraft' => true })
  107. render json: json
  108. end
  109. private
  110. def set_status
  111. @status = Status.find(params[:id])
  112. authorize @status, :show?
  113. rescue Mastodon::NotPermittedError
  114. not_found
  115. end
  116. def set_thread
  117. @thread = Status.find(status_params[:in_reply_to_id]) if status_params[:in_reply_to_id].present?
  118. authorize(@thread, :show?) if @thread.present?
  119. rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
  120. render json: { error: I18n.t('statuses.errors.in_reply_not_found') }, status: 404
  121. end
  122. def status_params
  123. params.permit(
  124. :status,
  125. :in_reply_to_id,
  126. :sensitive,
  127. :spoiler_text,
  128. :visibility,
  129. :language,
  130. :scheduled_at,
  131. :content_type,
  132. allowed_mentions: [],
  133. media_ids: [],
  134. media_attributes: [
  135. :id,
  136. :thumbnail,
  137. :description,
  138. :focus,
  139. ],
  140. poll: [
  141. :multiple,
  142. :hide_totals,
  143. :expires_in,
  144. options: [],
  145. ]
  146. )
  147. end
  148. def pagination_params(core_params)
  149. params.slice(:limit).permit(:limit).merge(core_params)
  150. end
  151. end