diff options
Diffstat (limited to 'app/services')
-rw-r--r-- | app/services/activitypub/fetch_remote_poll_service.rb | 52 | ||||
-rw-r--r-- | app/services/activitypub/fetch_replies_service.rb | 4 | ||||
-rw-r--r-- | app/services/post_status_service.rb | 11 | ||||
-rw-r--r-- | app/services/resolve_url_service.rb | 2 | ||||
-rw-r--r-- | app/services/suspend_account_service.rb | 2 | ||||
-rw-r--r-- | app/services/vote_service.rb | 40 |
6 files changed, 105 insertions, 6 deletions
diff --git a/app/services/activitypub/fetch_remote_poll_service.rb b/app/services/activitypub/fetch_remote_poll_service.rb new file mode 100644 index 000000000..1dd587d73 --- /dev/null +++ b/app/services/activitypub/fetch_remote_poll_service.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +class ActivityPub::FetchRemotePollService < BaseService + include JsonLdHelper + + def call(poll, on_behalf_of = nil) + @json = fetch_resource(poll.status.uri, true, on_behalf_of) + + return unless supported_context? && expected_type? + + expires_at = begin + if @json['closed'].is_a?(String) + @json['closed'] + elsif !@json['closed'].nil? && !@json['closed'].is_a?(FalseClass) + Time.now.utc + else + @json['endTime'] + end + end + + items = begin + if @json['anyOf'].is_a?(Array) + @json['anyOf'] + else + @json['oneOf'] + end + end + + latest_options = items.map { |item| item['name'].presence || item['content'] } + + # If for some reasons the options were changed, it invalidates all previous + # votes, so we need to remove them + poll.votes.delete_all if latest_options != poll.options + + poll.update!( + last_fetched_at: Time.now.utc, + expires_at: expires_at, + options: latest_options, + cached_tallies: items.map { |item| item.dig('replies', 'totalItems') || 0 } + ) + end + + private + + def supported_context? + super(@json) + end + + def expected_type? + equals_or_includes_any?(@json['type'], %w(Question)) + end +end diff --git a/app/services/activitypub/fetch_replies_service.rb b/app/services/activitypub/fetch_replies_service.rb index 95c486a43..569d0d7c1 100644 --- a/app/services/activitypub/fetch_replies_service.rb +++ b/app/services/activitypub/fetch_replies_service.rb @@ -36,9 +36,7 @@ class ActivityPub::FetchRepliesService < BaseService return collection_or_uri if collection_or_uri.is_a?(Hash) return unless @allow_synchronous_requests return if invalid_origin?(collection_or_uri) - collection = fetch_resource_without_id_validation(collection_or_uri) - raise Mastodon::UnexpectedResponseError if collection.nil? - collection + fetch_resource_without_id_validation(collection_or_uri, nil, true) end def filtered_replies diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index cfb266fbb..8a9d26c56 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -15,6 +15,7 @@ class PostStatusService < BaseService # @option [String] :spoiler_text # @option [String] :language # @option [String] :scheduled_at + # @option [Hash] :poll Optional poll to attach # @option [Enumerable] :media_ids Optional array of media IDs to attach # @option [Doorkeeper::Application] :application # @option [String] :idempotency Optional idempotency key @@ -28,6 +29,7 @@ class PostStatusService < BaseService return idempotency_duplicate if idempotency_given? && idempotency_duplicate? validate_media! + validate_poll! preprocess_attributes! if scheduled? @@ -98,13 +100,19 @@ class PostStatusService < BaseService def validate_media! return if @options[:media_ids].blank? || !@options[:media_ids].is_a?(Enumerable) - raise Mastodon::ValidationError, I18n.t('media_attachments.validations.too_many') if @options[:media_ids].size > 4 + raise Mastodon::ValidationError, I18n.t('media_attachments.validations.too_many') if @options[:media_ids].size > 4 || @options[:poll].present? @media = @account.media_attachments.where(status_id: nil).where(id: @options[:media_ids].take(4).map(&:to_i)) raise Mastodon::ValidationError, I18n.t('media_attachments.validations.images_and_video') if @media.size > 1 && @media.find(&:video?) end + def validate_poll! + return if @options[:poll].blank? + + @poll = @account.polls.new(@options[:poll]) + end + def language_from_option(str) ISO_639.find(str)&.alpha2 end @@ -157,6 +165,7 @@ class PostStatusService < BaseService text: @text, media_attachments: @media || [], thread: @in_reply_to, + owned_poll: @poll, sensitive: (@options[:sensitive].nil? ? @account.user&.setting_default_sensitive : @options[:sensitive]) || @options[:spoiler_text].present?, spoiler_text: @options[:spoiler_text] || '', visibility: @visibility, diff --git a/app/services/resolve_url_service.rb b/app/services/resolve_url_service.rb index ed0c56923..b98759bf6 100644 --- a/app/services/resolve_url_service.rb +++ b/app/services/resolve_url_service.rb @@ -20,7 +20,7 @@ class ResolveURLService < BaseService def process_url if equals_or_includes_any?(type, %w(Application Group Organization Person Service)) FetchRemoteAccountService.new.call(atom_url, body, protocol) - elsif equals_or_includes_any?(type, %w(Note Article Image Video Page)) + elsif equals_or_includes_any?(type, %w(Note Article Image Video Page Question)) FetchRemoteStatusService.new.call(atom_url, body, protocol) end end diff --git a/app/services/suspend_account_service.rb b/app/services/suspend_account_service.rb index fc3bc03a5..b2ae3a47c 100644 --- a/app/services/suspend_account_service.rb +++ b/app/services/suspend_account_service.rb @@ -84,7 +84,7 @@ class SuspendAccountService < BaseService @account.locked = false @account.display_name = '' @account.note = '' - @account.fields = {} + @account.fields = [] @account.statuses_count = 0 @account.followers_count = 0 @account.following_count = 0 diff --git a/app/services/vote_service.rb b/app/services/vote_service.rb new file mode 100644 index 000000000..8bab2810e --- /dev/null +++ b/app/services/vote_service.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +class VoteService < BaseService + include Authorization + + def call(account, poll, choices) + authorize_with account, poll, :vote? + + @account = account + @poll = poll + @choices = choices + @votes = [] + + ApplicationRecord.transaction do + @choices.each do |choice| + @votes << @poll.votes.create!(account: @account, choice: choice) + end + end + + return if @poll.account.local? + + @votes.each do |vote| + ActivityPub::DeliveryWorker.perform_async( + build_json(vote), + @account.id, + @poll.account.inbox_url + ) + end + end + + private + + def build_json(vote) + ActiveModelSerializers::SerializableResource.new( + vote, + serializer: ActivityPub::VoteSerializer, + adapter: ActivityPub::Adapter + ).to_json + end +end |