diff options
author | Starfall <us@starfall.systems> | 2022-02-13 22:15:26 -0600 |
---|---|---|
committer | Starfall <us@starfall.systems> | 2022-02-13 22:15:26 -0600 |
commit | c0341f06be5310a00b85a5d48fa80891d47c6710 (patch) | |
tree | 907ef7f787f8bd446a6d9be1448a8bcff74e5a08 /app/services | |
parent | 169688aa9f2a69ac3d36332c833e9cad43b5f7a5 (diff) | |
parent | 6f78c66fe01921a4e7e01aa6e2386a5fce7f3afd (diff) |
Merge remote-tracking branch 'glitch/main'
Not at all sure where the admin UI is going to display English language names now but OK.
Diffstat (limited to 'app/services')
-rw-r--r-- | app/services/activitypub/process_status_update_service.rb | 26 | ||||
-rw-r--r-- | app/services/concerns/payloadable.rb | 18 | ||||
-rw-r--r-- | app/services/delete_account_service.rb | 2 | ||||
-rw-r--r-- | app/services/fan_out_on_write_service.rb | 9 | ||||
-rw-r--r-- | app/services/notify_service.rb | 4 | ||||
-rw-r--r-- | app/services/post_status_service.rb | 7 | ||||
-rw-r--r-- | app/services/process_hashtags_service.rb | 42 | ||||
-rw-r--r-- | app/services/remove_status_service.rb | 2 | ||||
-rw-r--r-- | app/services/report_service.rb | 12 | ||||
-rw-r--r-- | app/services/update_status_service.rb | 153 |
10 files changed, 231 insertions, 44 deletions
diff --git a/app/services/activitypub/process_status_update_service.rb b/app/services/activitypub/process_status_update_service.rb index 977928127..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 @@ -120,7 +118,7 @@ class ActivityPub::ProcessStatusUpdateService < BaseService @status.text = @status_parser.text || '' @status.spoiler_text = @status_parser.spoiler_text || '' @status.sensitive = @account.sensitized? || @status_parser.sensitive || false - @status.language = @status_parser.language || detected_language + @status.language = @status_parser.language @status.edited_at = @status_parser.edited_at || Time.now.utc if significant_changes? @status.save! @@ -210,10 +208,6 @@ class ActivityPub::ProcessStatusUpdateService < BaseService { redis: Redis.current, key: "create:#{@uri}", autorelease: 15.minutes.seconds } end - def detected_language - LanguageDetector.instance.detect(@status_parser.text, @account) - 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 @@ -221,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/concerns/payloadable.rb b/app/services/concerns/payloadable.rb index 3e45570c3..04c3798fe 100644 --- a/app/services/concerns/payloadable.rb +++ b/app/services/concerns/payloadable.rb @@ -1,13 +1,21 @@ # frozen_string_literal: true module Payloadable + # @param [ActiveModelSerializers::Model] record + # @param [ActiveModelSerializers::Serializer] serializer + # @param [Hash] options + # @option options [Account] :signer + # @option options [String] :sign_with + # @option options [Boolean] :always_sign + # @return [Hash] def serialize_payload(record, serializer, options = {}) - signer = options.delete(:signer) - sign_with = options.delete(:sign_with) - payload = ActiveModelSerializers::SerializableResource.new(record, options.merge(serializer: serializer, adapter: ActivityPub::Adapter)).as_json - object = record.respond_to?(:virtual_object) ? record.virtual_object : record + signer = options.delete(:signer) + sign_with = options.delete(:sign_with) + always_sign = options.delete(:always_sign) + payload = ActiveModelSerializers::SerializableResource.new(record, options.merge(serializer: serializer, adapter: ActivityPub::Adapter)).as_json + object = record.respond_to?(:virtual_object) ? record.virtual_object : record - if (object.respond_to?(:sign?) && object.sign?) && signer && signing_enabled? + if (object.respond_to?(:sign?) && object.sign?) && signer && (always_sign || signing_enabled?) ActivityPub::LinkedDataSignature.new(payload).sign!(signer, sign_with: sign_with) else payload diff --git a/app/services/delete_account_service.rb b/app/services/delete_account_service.rb index 0e3fedfe7..a572a7c59 100644 --- a/app/services/delete_account_service.rb +++ b/app/services/delete_account_service.rb @@ -265,7 +265,7 @@ class DeleteAccountService < BaseService end def delete_actor_json - @delete_actor_json ||= Oj.dump(serialize_payload(@account, ActivityPub::DeleteActorSerializer, signer: @account)) + @delete_actor_json ||= Oj.dump(serialize_payload(@account, ActivityPub::DeleteActorSerializer, signer: @account, always_sign: true)) end def delivery_inboxes diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb index 46feec5aa..08fae7935 100644 --- a/app/services/fan_out_on_write_service.rb +++ b/app/services/fan_out_on_write_service.rb @@ -34,6 +34,7 @@ class FanOutOnWriteService < BaseService def fan_out_to_local_recipients! deliver_to_self! notify_mentioned_accounts! + notify_about_update! if update? case @status.visibility.to_sym when :public, :unlisted, :private @@ -66,6 +67,14 @@ class FanOutOnWriteService < BaseService end end + def notify_about_update! + @status.reblogged_by_accounts.merge(Account.local).select(:id).reorder(nil).find_in_batches do |accounts| + LocalNotificationWorker.push_bulk(accounts) do |account| + [account.id, @status.id, 'Status', 'update'] + end + end + end + def deliver_to_all_followers! @account.followers_for_local_distribution.select(:id).reorder(nil).find_in_batches do |followers| FeedInsertWorker.push_bulk(followers) do |follower| diff --git a/app/services/notify_service.rb b/app/services/notify_service.rb index 0f3516d28..039e007f5 100644 --- a/app/services/notify_service.rb +++ b/app/services/notify_service.rb @@ -46,6 +46,10 @@ class NotifyService < BaseService false end + def blocked_update? + false + end + def following_sender? return @following_sender if defined?(@following_sender) @following_sender = @recipient.following?(@notification.from_account) || @recipient.requested?(@notification.from_account) diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index 9d26e0f5b..c5061dd63 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -2,6 +2,7 @@ class PostStatusService < BaseService include Redisable + include LanguagesHelper MIN_SCHEDULE_OFFSET = 5.minutes.freeze @@ -118,10 +119,6 @@ class PostStatusService < BaseService raise Mastodon::ValidationError, I18n.t('media_attachments.validations.not_ready') if @media.any?(&:not_processed?) end - def language_from_option(str) - ISO_639.find(str)&.alpha2 - end - def process_mentions_service ProcessMentionsService.new end @@ -174,7 +171,7 @@ class PostStatusService < BaseService sensitive: @sensitive, spoiler_text: @options[:spoiler_text] || '', visibility: @visibility, - language: language_from_option(@options[:language]) || @account.user&.setting_default_language&.presence || LanguageDetector.instance.detect(@text, @account), + language: valid_locale_or_nil(@options[:language].presence || @account.user&.preferred_posting_language || I18n.default_locale), application: @options[:application], content_type: @options[:content_type] || @account.user&.setting_default_content_type, rate_limit: @options[:with_rate_limit], 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/remove_status_service.rb b/app/services/remove_status_service.rb index e41ad2b0a..361cd4d82 100644 --- a/app/services/remove_status_service.rb +++ b/app/services/remove_status_service.rb @@ -97,7 +97,7 @@ class RemoveStatusService < BaseService end def signed_activity_json - @signed_activity_json ||= Oj.dump(serialize_payload(@status, @status.reblog? ? ActivityPub::UndoAnnounceSerializer : ActivityPub::DeleteSerializer, signer: @account)) + @signed_activity_json ||= Oj.dump(serialize_payload(@status, @status.reblog? ? ActivityPub::UndoAnnounceSerializer : ActivityPub::DeleteSerializer, signer: @account, always_sign: true)) end def remove_reblogs 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..76530a54c --- /dev/null +++ b/app/services/update_status_service.rb @@ -0,0 +1,153 @@ +# 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 + # @option options [String] :content_type + 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.content_type = @options[:content_type] || @status.content_type + @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) unless @status.local_only? + 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 |