diff options
Diffstat (limited to 'app/services')
-rw-r--r-- | app/services/activitypub/process_status_update_service.rb | 20 | ||||
-rw-r--r-- | app/services/process_hashtags_service.rb | 42 | ||||
-rw-r--r-- | app/services/report_service.rb | 12 | ||||
-rw-r--r-- | app/services/update_status_service.rb | 151 |
4 files changed, 198 insertions, 27 deletions
diff --git a/app/services/activitypub/process_status_update_service.rb b/app/services/activitypub/process_status_update_service.rb index b1cea1cdf..7438a7c53 100644 --- a/app/services/activitypub/process_status_update_service.rb +++ b/app/services/activitypub/process_status_update_service.rb @@ -95,10 +95,7 @@ class ActivityPub::ProcessStatusUpdateService < BaseService # If for some reasons the options were changed, it invalidates all previous # votes, so we need to remove them - if poll_parser.significantly_changes?(poll) - @poll_changed = true - poll.votes.delete_all unless poll.new_record? - end + @poll_changed = true if poll_parser.significantly_changes?(poll) poll.last_fetched_at = Time.now.utc poll.options = poll_parser.options @@ -106,6 +103,7 @@ class ActivityPub::ProcessStatusUpdateService < BaseService poll.expires_at = poll_parser.expires_at poll.voters_count = poll_parser.voters_count poll.cached_tallies = poll_parser.cached_tallies + poll.reset_votes! if @poll_changed poll.save! @status.poll_id = poll.id @@ -217,24 +215,18 @@ class ActivityPub::ProcessStatusUpdateService < BaseService return if @status.edits.any? - @status.edits.create( - text: @status.text, - spoiler_text: @status.spoiler_text, + @status.snapshot!( media_attachments_changed: false, - account_id: @account.id, - created_at: @status.created_at + at_time: @status.created_at ) end def create_edit! return unless significant_changes? - @status_edit = @status.edits.create( - text: @status.text, - spoiler_text: @status.spoiler_text, + @status.snapshot!( media_attachments_changed: @media_attachments_changed || @poll_changed, - account_id: @account.id, - created_at: @status.edited_at + account_id: @account.id ) end diff --git a/app/services/process_hashtags_service.rb b/app/services/process_hashtags_service.rb index 47277c56c..43d7bcca6 100644 --- a/app/services/process_hashtags_service.rb +++ b/app/services/process_hashtags_service.rb @@ -1,20 +1,40 @@ # frozen_string_literal: true class ProcessHashtagsService < BaseService - def call(status, tags = []) - tags = Extractor.extract_hashtags(status.text) if status.local? - records = [] - - Tag.find_or_create_by_names(tags) do |tag| - status.tags << tag - records << tag - tag.update(last_status_at: status.created_at) if tag.last_status_at.nil? || (tag.last_status_at < status.created_at && tag.last_status_at < 12.hours.ago) + def call(status, raw_tags = []) + @status = status + @account = status.account + @raw_tags = status.local? ? Extractor.extract_hashtags(status.text) : raw_tags + @previous_tags = status.tags.to_a + @current_tags = [] + + assign_tags! + update_featured_tags! + end + + private + + def assign_tags! + @status.tags = @current_tags = Tag.find_or_create_by_names(@raw_tags) + end + + def update_featured_tags! + return unless @status.distributable? + + added_tags = @current_tags - @previous_tags + + unless added_tags.empty? + @account.featured_tags.where(tag_id: added_tags.map(&:id)).each do |featured_tag| + featured_tag.increment(@status.created_at) + end end - return unless status.distributable? + removed_tags = @previous_tags - @current_tags - status.account.featured_tags.where(tag_id: records.map(&:id)).each do |featured_tag| - featured_tag.increment(status.created_at) + unless removed_tags.empty? + @account.featured_tags.where(tag_id: removed_tags.map(&:id)).each do |featured_tag| + featured_tag.decrement(@status.id) + end end end end diff --git a/app/services/report_service.rb b/app/services/report_service.rb index bc0a8b464..caf99ab6e 100644 --- a/app/services/report_service.rb +++ b/app/services/report_service.rb @@ -8,13 +8,15 @@ class ReportService < BaseService @target_account = target_account @status_ids = options.delete(:status_ids) || [] @comment = options.delete(:comment) || '' + @category = options.delete(:category) || 'other' + @rule_ids = options.delete(:rule_ids) @options = options raise ActiveRecord::RecordNotFound if @target_account.suspended? create_report! notify_staff! - forward_to_origin! if !@target_account.local? && ActiveModel::Type::Boolean.new.cast(@options[:forward]) + forward_to_origin! if forward? @report end @@ -27,7 +29,9 @@ class ReportService < BaseService status_ids: @status_ids, comment: @comment, uri: @options[:uri], - forwarded: ActiveModel::Type::Boolean.new.cast(@options[:forward]) + forwarded: forward?, + category: @category, + rule_ids: @rule_ids ) end @@ -48,6 +52,10 @@ class ReportService < BaseService ) end + def forward? + !@target_account.local? && ActiveModel::Type::Boolean.new.cast(@options[:forward]) + end + def payload Oj.dump(serialize_payload(@report, ActivityPub::FlagSerializer, account: some_local_account)) end diff --git a/app/services/update_status_service.rb b/app/services/update_status_service.rb new file mode 100644 index 000000000..238ef0755 --- /dev/null +++ b/app/services/update_status_service.rb @@ -0,0 +1,151 @@ +# frozen_string_literal: true + +class UpdateStatusService < BaseService + include Redisable + include LanguagesHelper + + # @param [Status] status + # @param [Integer] account_id + # @param [Hash] options + # @option options [Array<Integer>] :media_ids + # @option options [Hash] :poll + # @option options [String] :text + # @option options [String] :spoiler_text + # @option options [Boolean] :sensitive + # @option options [String] :language + def call(status, account_id, options = {}) + @status = status + @options = options + @account_id = account_id + @media_attachments_changed = false + @poll_changed = false + + Status.transaction do + create_previous_edit! + update_media_attachments! + update_poll! + update_immediate_attributes! + create_edit! + end + + queue_poll_notifications! + reset_preview_card! + update_metadata! + broadcast_updates! + + @status + end + + private + + def update_media_attachments! + previous_media_attachments = @status.media_attachments.to_a + next_media_attachments = validate_media! + removed_media_attachments = previous_media_attachments - next_media_attachments + added_media_attachments = next_media_attachments - previous_media_attachments + + MediaAttachment.where(id: removed_media_attachments.map(&:id)).update_all(status_id: nil) + MediaAttachment.where(id: added_media_attachments.map(&:id)).update_all(status_id: @status.id) + + @status.media_attachments.reload + @media_attachments_changed = true if removed_media_attachments.any? || added_media_attachments.any? + end + + 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 || @options[:poll].present? + + media_attachments = @status.account.media_attachments.where(status_id: [nil, @status.id]).where(scheduled_status_id: nil).where(id: @options[:media_ids].take(4).map(&:to_i)).to_a + + raise Mastodon::ValidationError, I18n.t('media_attachments.validations.images_and_video') if media_attachments.size > 1 && media_attachments.find(&:audio_or_video?) + raise Mastodon::ValidationError, I18n.t('media_attachments.validations.not_ready') if media_attachments.any?(&:not_processed?) + + media_attachments + end + + def update_poll! + previous_poll = @status.preloadable_poll + @previous_expires_at = previous_poll&.expires_at + + if @options[:poll].present? + poll = previous_poll || @status.account.polls.new(status: @status, votes_count: 0) + + # If for some reasons the options were changed, it invalidates all previous + # votes, so we need to remove them + @poll_changed = true if @options[:poll][:options] != poll.options || ActiveModel::Type::Boolean.new.cast(@options[:poll][:multiple]) != poll.multiple + + poll.options = @options[:poll][:options] + poll.hide_totals = @options[:poll][:hide_totals] || false + poll.multiple = @options[:poll][:multiple] || false + poll.expires_in = @options[:poll][:expires_in] + poll.reset_votes! if @poll_changed + poll.save! + + @status.poll_id = poll.id + elsif previous_poll.present? + previous_poll.destroy + @poll_changed = true + @status.poll_id = nil + end + end + + def update_immediate_attributes! + @status.text = @options[:text].presence || @options.delete(:spoiler_text) || '' + @status.spoiler_text = @options[:spoiler_text] || '' + @status.sensitive = @options[:sensitive] || @options[:spoiler_text].present? + @status.language = valid_locale_or_nil(@options[:language] || @status.language || @status.account.user&.preferred_posting_language || I18n.default_locale) + @status.edited_at = Time.now.utc + + @status.save! + end + + def reset_preview_card! + return unless @status.text_previously_changed? + + @status.preview_cards.clear + LinkCrawlWorker.perform_async(@status.id) + end + + def update_metadata! + ProcessHashtagsService.new.call(@status) + ProcessMentionsService.new.call(@status) + end + + def broadcast_updates! + DistributionWorker.perform_async(@status.id, { 'update' => true }) + ActivityPub::StatusUpdateDistributionWorker.perform_async(@status.id) + end + + def queue_poll_notifications! + poll = @status.preloadable_poll + + # If the poll had no expiration date set but now has, or now has a sooner + # expiration date, and people have voted, schedule a notification + + return unless poll.present? && poll.expires_at.present? && poll.votes.exists? + + PollExpirationNotifyWorker.remove_from_scheduled(poll.id) if @previous_expires_at.present? && @previous_expires_at > poll.expires_at + PollExpirationNotifyWorker.perform_at(poll.expires_at + 5.minutes, poll.id) + end + + def create_previous_edit! + # We only need to create a previous edit when no previous edits exist, e.g. + # when the status has never been edited. For other cases, we always create + # an edit, so the step can be skipped + + return if @status.edits.any? + + @status.snapshot!( + media_attachments_changed: false, + at_time: @status.created_at + ) + end + + def create_edit! + @status.snapshot!( + media_attachments_changed: @media_attachments_changed || @poll_changed, + account_id: @account_id + ) + end +end |