diff options
Diffstat (limited to 'app/services')
41 files changed, 281 insertions, 185 deletions
diff --git a/app/services/account_search_service.rb b/app/services/account_search_service.rb index 85538870b..dfc3a45f8 100644 --- a/app/services/account_search_service.rb +++ b/app/services/account_search_service.rb @@ -32,15 +32,13 @@ class AccountSearchService < BaseService return @exact_match if defined?(@exact_match) - match = begin - if options[:resolve] - ResolveAccountService.new.call(query) - elsif domain_is_local? - Account.find_local(query_username) - else - Account.find_remote(query_username, query_domain) - end - end + match = if options[:resolve] + ResolveAccountService.new.call(query) + elsif domain_is_local? + Account.find_local(query_username) + else + Account.find_remote(query_username, query_domain) + end match = nil if !match.nil? && !account.nil? && options[:following] && !account.following?(match) diff --git a/app/services/activitypub/fetch_featured_collection_service.rb b/app/services/activitypub/fetch_featured_collection_service.rb index a746ef4d6..1208820df 100644 --- a/app/services/activitypub/fetch_featured_collection_service.rb +++ b/app/services/activitypub/fetch_featured_collection_service.rb @@ -53,7 +53,7 @@ class ActivityPub::FetchFeaturedCollectionService < BaseService status.id rescue ActiveRecord::RecordInvalid => e - Rails.logger.debug "Invalid pinned status #{uri}: #{e.message}" + Rails.logger.debug { "Invalid pinned status #{uri}: #{e.message}" } nil end diff --git a/app/services/activitypub/fetch_featured_tags_collection_service.rb b/app/services/activitypub/fetch_featured_tags_collection_service.rb index ab047a0f8..ff1a88aa1 100644 --- a/app/services/activitypub/fetch_featured_tags_collection_service.rb +++ b/app/services/activitypub/fetch_featured_tags_collection_service.rb @@ -22,14 +22,12 @@ class ActivityPub::FetchFeaturedTagsCollectionService < BaseService collection = fetch_collection(collection['first']) if collection['first'].present? while collection.is_a?(Hash) - items = begin - case collection['type'] - when 'Collection', 'CollectionPage' - collection['items'] - when 'OrderedCollection', 'OrderedCollectionPage' - collection['orderedItems'] - end - end + items = case collection['type'] + when 'Collection', 'CollectionPage' + collection['items'] + when 'OrderedCollection', 'OrderedCollectionPage' + collection['orderedItems'] + end break if items.blank? diff --git a/app/services/activitypub/fetch_remote_account_service.rb b/app/services/activitypub/fetch_remote_account_service.rb index 7aba8269e..567dd8a14 100644 --- a/app/services/activitypub/fetch_remote_account_service.rb +++ b/app/services/activitypub/fetch_remote_account_service.rb @@ -6,7 +6,7 @@ class ActivityPub::FetchRemoteAccountService < ActivityPub::FetchRemoteActorServ actor = super return actor if actor.nil? || actor.is_a?(Account) - Rails.logger.debug "Fetching account #{uri} failed: Expected Account, got #{actor.class.name}" + Rails.logger.debug { "Fetching account #{uri} failed: Expected Account, got #{actor.class.name}" } raise Error, "Expected Account, got #{actor.class.name}" unless suppress_errors end end diff --git a/app/services/activitypub/fetch_remote_actor_service.rb b/app/services/activitypub/fetch_remote_actor_service.rb index a25fa54c4..c29570086 100644 --- a/app/services/activitypub/fetch_remote_actor_service.rb +++ b/app/services/activitypub/fetch_remote_actor_service.rb @@ -28,6 +28,7 @@ class ActivityPub::FetchRemoteActorService < BaseService raise Error, "Unsupported JSON-LD context for document #{uri}" unless supported_context? raise Error, "Unexpected object type for actor #{uri} (expected any of: #{SUPPORTED_TYPES})" unless expected_type? raise Error, "Actor #{uri} has moved to #{@json['movedTo']}" if break_on_redirect && @json['movedTo'].present? + raise Error, "Actor #{uri} has no 'preferredUsername', which is a requirement for Mastodon compatibility" if @json['preferredUsername'].blank? @uri = @json['id'] @username = @json['preferredUsername'] @@ -37,7 +38,7 @@ class ActivityPub::FetchRemoteActorService < BaseService ActivityPub::ProcessAccountService.new.call(@username, @domain, @json, only_key: only_key, verified_webfinger: !only_key, request_id: request_id) rescue Error => e - Rails.logger.debug "Fetching actor #{uri} failed: #{e.message}" + Rails.logger.debug { "Fetching actor #{uri} failed: #{e.message}" } raise unless suppress_errors end @@ -49,15 +50,14 @@ class ActivityPub::FetchRemoteActorService < BaseService if @username.casecmp(confirmed_username).zero? && @domain.casecmp(confirmed_domain).zero? raise Error, "Webfinger response for #{@username}@#{@domain} does not loop back to #{@uri}" if webfinger.link('self', 'href') != @uri + return end webfinger = webfinger!("acct:#{confirmed_username}@#{confirmed_domain}") @username, @domain = split_acct(webfinger.subject) - unless confirmed_username.casecmp(@username).zero? && confirmed_domain.casecmp(@domain).zero? - raise Webfinger::RedirectError, "Too many webfinger redirects for URI #{@uri} (stopped at #{@username}@#{@domain})" - end + raise Webfinger::RedirectError, "Too many webfinger redirects for URI #{@uri} (stopped at #{@username}@#{@domain})" unless confirmed_username.casecmp(@username).zero? && confirmed_domain.casecmp(@domain).zero? raise Error, "Webfinger response for #{@username}@#{@domain} does not loop back to #{@uri}" if webfinger.link('self', 'href') != @uri rescue Webfinger::RedirectError => e diff --git a/app/services/activitypub/fetch_remote_key_service.rb b/app/services/activitypub/fetch_remote_key_service.rb index 32e82b47a..8eb97c1e6 100644 --- a/app/services/activitypub/fetch_remote_key_service.rb +++ b/app/services/activitypub/fetch_remote_key_service.rb @@ -38,7 +38,7 @@ class ActivityPub::FetchRemoteKeyService < BaseService find_actor(owner_uri, @owner, suppress_errors) rescue Error => e - Rails.logger.debug "Fetching key #{uri} failed: #{e.message}" + Rails.logger.debug { "Fetching key #{uri} failed: #{e.message}" } raise unless suppress_errors end diff --git a/app/services/activitypub/fetch_remote_status_service.rb b/app/services/activitypub/fetch_remote_status_service.rb index 21b9242f8..ab0acf7f0 100644 --- a/app/services/activitypub/fetch_remote_status_service.rb +++ b/app/services/activitypub/fetch_remote_status_service.rb @@ -2,17 +2,18 @@ class ActivityPub::FetchRemoteStatusService < BaseService include JsonLdHelper + include Redisable + + DISCOVERIES_PER_REQUEST = 1000 # Should be called when uri has already been checked for locality def call(uri, id: true, prefetched_body: nil, on_behalf_of: nil, expected_actor_uri: nil, request_id: nil) - @request_id = request_id - @json = begin - if prefetched_body.nil? - fetch_resource(uri, id, on_behalf_of) - else - body_to_json(prefetched_body, compare_id: id ? uri : nil) - end - end + @request_id = request_id || "#{Time.now.utc.to_i}-status-#{uri}" + @json = if prefetched_body.nil? + fetch_resource(uri, id, on_behalf_of) + else + body_to_json(prefetched_body, compare_id: id ? uri : nil) + end return unless supported_context? @@ -42,13 +43,20 @@ class ActivityPub::FetchRemoteStatusService < BaseService # activity as an update rather than create activity_json['type'] = 'Update' if equals_or_includes_any?(activity_json['type'], %w(Create)) && Status.where(uri: object_uri, account_id: actor.id).exists? - ActivityPub::Activity.factory(activity_json, actor, request_id: request_id).perform + with_redis do |redis| + discoveries = redis.incr("status_discovery_per_request:#{@request_id}") + redis.expire("status_discovery_per_request:#{@request_id}", 5.minutes.seconds) + return nil if discoveries > DISCOVERIES_PER_REQUEST + end + + ActivityPub::Activity.factory(activity_json, actor, request_id: @request_id).perform end private def trustworthy_attribution?(uri, attributed_to) return false if uri.nil? || attributed_to.nil? + Addressable::URI.parse(uri).normalized_host.casecmp(Addressable::URI.parse(attributed_to).normalized_host).zero? end diff --git a/app/services/activitypub/fetch_replies_service.rb b/app/services/activitypub/fetch_replies_service.rb index 8cb309e52..3fe150ba2 100644 --- a/app/services/activitypub/fetch_replies_service.rb +++ b/app/services/activitypub/fetch_replies_service.rb @@ -3,14 +3,14 @@ class ActivityPub::FetchRepliesService < BaseService include JsonLdHelper - def call(parent_status, collection_or_uri, allow_synchronous_requests = true) + def call(parent_status, collection_or_uri, allow_synchronous_requests: true, request_id: nil) @account = parent_status.account @allow_synchronous_requests = allow_synchronous_requests @items = collection_items(collection_or_uri) return if @items.nil? - FetchReplyWorker.push_bulk(filtered_replies) + FetchReplyWorker.push_bulk(filtered_replies) { |reply_uri| [reply_uri, { 'request_id' => request_id }] } @items end @@ -36,6 +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) + fetch_resource_without_id_validation(collection_or_uri, nil, true) end diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb index 2da9096c7..603e4cf48 100644 --- a/app/services/activitypub/process_account_service.rb +++ b/app/services/activitypub/process_account_service.rb @@ -226,6 +226,7 @@ class ActivityPub::ProcessAccountService < BaseService def property_values return unless @json['attachment'].is_a?(Array) + as_array(@json['attachment']).select { |attachment| attachment['type'] == 'PropertyValue' }.map { |attachment| attachment.slice('name', 'value') } end @@ -289,6 +290,7 @@ class ActivityPub::ProcessAccountService < BaseService def domain_block return @domain_block if defined?(@domain_block) + @domain_block = DomainBlock.rule_for(@domain) end diff --git a/app/services/activitypub/process_collection_service.rb b/app/services/activitypub/process_collection_service.rb index fffe30195..52f48bd49 100644 --- a/app/services/activitypub/process_collection_service.rb +++ b/app/services/activitypub/process_collection_service.rb @@ -11,7 +11,7 @@ class ActivityPub::ProcessCollectionService < BaseService begin @json = compact(@json) if @json['signature'].is_a?(Hash) rescue JSON::LD::JsonLdError => e - Rails.logger.debug "Error when compacting JSON-LD document for #{value_or_id(@json['actor'])}: #{e.message}" + Rails.logger.debug { "Error when compacting JSON-LD document for #{value_or_id(@json['actor'])}: #{e.message}" } @json = original_json.without('signature') end @@ -71,8 +71,8 @@ class ActivityPub::ProcessCollectionService < BaseService @account = ActivityPub::LinkedDataSignature.new(@json).verify_actor! @account = nil unless @account.is_a?(Account) @account - rescue JSON::LD::JsonLdError => e - Rails.logger.debug "Could not verify LD-Signature for #{value_or_id(@json['actor'])}: #{e.message}" + rescue JSON::LD::JsonLdError, RDF::WriterError => e + Rails.logger.debug { "Could not verify LD-Signature for #{value_or_id(@json['actor'])}: #{e.message}" } nil end end diff --git a/app/services/activitypub/process_status_update_service.rb b/app/services/activitypub/process_status_update_service.rb index 11b38ab92..ac7372f74 100644 --- a/app/services/activitypub/process_status_update_service.rb +++ b/app/services/activitypub/process_status_update_service.rb @@ -80,9 +80,7 @@ class ActivityPub::ProcessStatusUpdateService < BaseService # If a previously existing media attachment was significantly updated, mark # media attachments as changed even if none were added or removed - if media_attachment_parser.significantly_changes?(media_attachment) - @media_attachments_changed = true - end + @media_attachments_changed = true if media_attachment_parser.significantly_changes?(media_attachment) media_attachment.description = media_attachment_parser.description media_attachment.focus = media_attachment_parser.focus @@ -94,7 +92,7 @@ class ActivityPub::ProcessStatusUpdateService < BaseService @next_media_attachments << media_attachment rescue Addressable::URI::InvalidURIError => e - Rails.logger.debug "Invalid URL in attachment: #{e}" + Rails.logger.debug { "Invalid URL in attachment: #{e}" } end end diff --git a/app/services/backup_service.rb b/app/services/backup_service.rb index f07f407d8..c5e7a8e58 100644 --- a/app/services/backup_service.rb +++ b/app/services/backup_service.rb @@ -23,7 +23,7 @@ class BackupService < BaseService account.statuses.with_includes.reorder(nil).find_in_batches do |statuses| statuses.each do |status| item = serialize_payload(ActivityPub::ActivityPresenter.from_status(status), ActivityPub::ActivitySerializer, signer: @account, allow_local_only: true) - item.delete(:'@context') + item.delete(:@context) unless item[:type] == 'Announce' || item[:object][:attachment].blank? item[:object][:attachment].each do |attachment| @@ -53,7 +53,7 @@ class BackupService < BaseService end end - archive_filename = ['archive', Time.now.utc.strftime('%Y%m%d%H%M%S'), SecureRandom.hex(16)].join('-') + '.tar.gz' + archive_filename = "#{['archive', Time.now.utc.strftime('%Y%m%d%H%M%S'), SecureRandom.hex(16)].join('-')}.tar.gz" @backup.dump = ActionDispatch::Http::UploadedFile.new(tempfile: tmp_file, filename: archive_filename) @backup.processed = true @@ -86,14 +86,14 @@ class BackupService < BaseService def dump_actor!(tar) actor = serialize(account, ActivityPub::ActorSerializer) - actor[:icon][:url] = 'avatar' + File.extname(actor[:icon][:url]) if actor[:icon] - actor[:image][:url] = 'header' + File.extname(actor[:image][:url]) if actor[:image] + actor[:icon][:url] = "avatar#{File.extname(actor[:icon][:url])}" if actor[:icon] + actor[:image][:url] = "header#{File.extname(actor[:image][:url])}" if actor[:image] actor[:outbox] = 'outbox.json' actor[:likes] = 'likes.json' actor[:bookmarks] = 'bookmarks.json' - download_to_tar(tar, account.avatar, 'avatar' + File.extname(account.avatar.path)) if account.avatar.exists? - download_to_tar(tar, account.header, 'header' + File.extname(account.header.path)) if account.header.exists? + download_to_tar(tar, account.avatar, "avatar#{File.extname(account.avatar.path)}") if account.avatar.exists? + download_to_tar(tar, account.header, "header#{File.extname(account.header.path)}") if account.header.exists? json = Oj.dump(actor) @@ -154,7 +154,7 @@ class BackupService < BaseService object, serializer: serializer, adapter: ActivityPub::Adapter, - allow_local_only: true, + allow_local_only: true ).as_json end diff --git a/app/services/batched_remove_status_service.rb b/app/services/batched_remove_status_service.rb index e2c370057..a48386ba2 100644 --- a/app/services/batched_remove_status_service.rb +++ b/app/services/batched_remove_status_service.rb @@ -48,9 +48,9 @@ class BatchedRemoveStatusService < BaseService # Cannot be batched @status_id_cutoff = Mastodon::Snowflake.id_at(2.weeks.ago) - redis.pipelined do + redis.pipelined do |pipeline| statuses.each do |status| - unpush_from_public_timelines(status) + unpush_from_public_timelines(status, pipeline) end end end @@ -73,22 +73,22 @@ class BatchedRemoveStatusService < BaseService end end - def unpush_from_public_timelines(status) + def unpush_from_public_timelines(status, pipeline) return unless status.public_visibility? && status.id > @status_id_cutoff payload = Oj.dump(event: :delete, payload: status.id.to_s) - redis.publish('timeline:public', payload) - redis.publish(status.local? ? 'timeline:public:local' : 'timeline:public:remote', payload) + pipeline.publish('timeline:public', payload) + pipeline.publish(status.local? ? 'timeline:public:local' : 'timeline:public:remote', payload) if status.media_attachments.any? - redis.publish('timeline:public:media', payload) - redis.publish(status.local? ? 'timeline:public:local:media' : 'timeline:public:remote:media', payload) + pipeline.publish('timeline:public:media', payload) + pipeline.publish(status.local? ? 'timeline:public:local:media' : 'timeline:public:remote:media', payload) end status.tags.map { |tag| tag.name.mb_chars.downcase }.each do |hashtag| - redis.publish("timeline:hashtag:#{hashtag}", payload) - redis.publish("timeline:hashtag:#{hashtag}:local", payload) if status.local? + pipeline.publish("timeline:hashtag:#{hashtag}", payload) + pipeline.publish("timeline:hashtag:#{hashtag}:local", payload) if status.local? end end diff --git a/app/services/delete_account_service.rb b/app/services/delete_account_service.rb index 3a082c7c8..190a72e5c 100644 --- a/app/services/delete_account_service.rb +++ b/app/services/delete_account_service.rb @@ -257,17 +257,17 @@ class DeleteAccountService < BaseService end def delete_actor! - ActivityPub::DeliveryWorker.push_bulk(delivery_inboxes) do |inbox_url| + ActivityPub::DeliveryWorker.push_bulk(delivery_inboxes, limit: 1_000) do |inbox_url| [delete_actor_json, @account.id, inbox_url] end - ActivityPub::LowPriorityDeliveryWorker.push_bulk(low_priority_delivery_inboxes) do |inbox_url| + ActivityPub::LowPriorityDeliveryWorker.push_bulk(low_priority_delivery_inboxes, limit: 1_000) do |inbox_url| [delete_actor_json, @account.id, inbox_url] end end def delete_actor_json - @delete_actor_json ||= Oj.dump(serialize_payload(@account, ActivityPub::DeleteActorSerializer)) + @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 8e74e152e..3b14a6748 100644 --- a/app/services/fan_out_on_write_service.rb +++ b/app/services/fan_out_on_write_service.rb @@ -116,7 +116,7 @@ class FanOutOnWriteService < BaseService end def deliver_to_direct_timelines! - FeedInsertWorker.push_bulk(@status.mentions.includes(:account).map(&:account).select { |mentioned_account| mentioned_account.local? }) do |account| + FeedInsertWorker.push_bulk(@status.mentions.includes(:account).map(&:account).select(&:local?)) do |account| [@status.id, account.id, 'direct', { 'update' => update? }] end end diff --git a/app/services/favourite_service.rb b/app/services/favourite_service.rb index dc7fe8855..6fdc92a17 100644 --- a/app/services/favourite_service.rb +++ b/app/services/favourite_service.rb @@ -40,6 +40,7 @@ class FavouriteService < BaseService def bump_potential_friendship(account, status) ActivityTracker.increment('activity:interactions') return if account.following?(status.account_id) + PotentialFriendshipTracker.record(account.id, status.account_id, :favourite) end diff --git a/app/services/fetch_link_card_service.rb b/app/services/fetch_link_card_service.rb index e5b5b730e..8d07958b7 100644 --- a/app/services/fetch_link_card_service.rb +++ b/app/services/fetch_link_card_service.rb @@ -30,7 +30,7 @@ class FetchLinkCardService < BaseService attach_card if @card&.persisted? rescue HTTP::Error, OpenSSL::SSL::SSLError, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError => e - Rails.logger.debug "Error fetching link #{@original_url}: #{e}" + Rails.logger.debug { "Error fetching link #{@original_url}: #{e}" } nil end @@ -45,7 +45,7 @@ class FetchLinkCardService < BaseService def html return @html if defined?(@html) - Request.new(:get, @url).add_headers('Accept' => 'text/html', 'User-Agent' => Mastodon::Version.user_agent + ' Bot').perform do |res| + Request.new(:get, @url).add_headers('Accept' => 'text/html', 'User-Agent' => "#{Mastodon::Version.user_agent} Bot").perform do |res| # We follow redirects, and ideally we want to save the preview card for # the destination URL and not any link shortener in-between, so here # we set the URL to the one of the last response in the redirect chain @@ -69,16 +69,14 @@ class FetchLinkCardService < BaseService end def parse_urls - urls = begin - if @status.local? - @status.text.scan(URL_PATTERN).map { |array| Addressable::URI.parse(array[1]).normalize } - else - document = Nokogiri::HTML(@status.text) - links = document.css('a') - - links.filter_map { |a| Addressable::URI.parse(a['href']) unless skip_link?(a) }.filter_map(&:normalize) - end - end + urls = if @status.local? + @status.text.scan(URL_PATTERN).map { |array| Addressable::URI.parse(array[1]).normalize } + else + document = Nokogiri::HTML(@status.text) + links = document.css('a') + + links.filter_map { |a| Addressable::URI.parse(a['href']) unless skip_link?(a) }.filter_map(&:normalize) + end urls.reject { |uri| bad_url?(uri) }.first end diff --git a/app/services/fetch_oembed_service.rb b/app/services/fetch_oembed_service.rb index 7d0879c79..9851ac098 100644 --- a/app/services/fetch_oembed_service.rb +++ b/app/services/fetch_oembed_service.rb @@ -82,7 +82,7 @@ class FetchOEmbedService return if @endpoint_url.blank? body = Request.new(:get, @endpoint_url).perform do |res| - res.code != 200 ? nil : res.body_with_limit + res.code == 200 ? res.body_with_limit : nil end validate(parse_for_format(body)) if body.present? diff --git a/app/services/fetch_remote_status_service.rb b/app/services/fetch_remote_status_service.rb index eafde4d4a..08c2d24ba 100644 --- a/app/services/fetch_remote_status_service.rb +++ b/app/services/fetch_remote_status_service.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class FetchRemoteStatusService < BaseService - def call(url, prefetched_body = nil) + def call(url, prefetched_body: nil, request_id: nil) if prefetched_body.nil? resource_url, resource_options = FetchResourceService.new.call(url) else @@ -9,6 +9,6 @@ class FetchRemoteStatusService < BaseService resource_options = { prefetched_body: prefetched_body } end - ActivityPub::FetchRemoteStatusService.new.call(resource_url, **resource_options) unless resource_url.nil? + ActivityPub::FetchRemoteStatusService.new.call(resource_url, **resource_options.merge(request_id: request_id)) unless resource_url.nil? end end diff --git a/app/services/fetch_resource_service.rb b/app/services/fetch_resource_service.rb index 73204e55d..4470fca01 100644 --- a/app/services/fetch_resource_service.rb +++ b/app/services/fetch_resource_service.rb @@ -12,7 +12,7 @@ class FetchResourceService < BaseService process(url) rescue HTTP::Error, OpenSSL::SSL::SSLError, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError => e - Rails.logger.debug "Error fetching resource #{@url}: #{e}" + Rails.logger.debug { "Error fetching resource #{@url}: #{e}" } nil end diff --git a/app/services/follow_migration_service.rb b/app/services/follow_migration_service.rb new file mode 100644 index 000000000..cfe9093cb --- /dev/null +++ b/app/services/follow_migration_service.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +class FollowMigrationService < FollowService + # Follow an account with the same settings as another account, and unfollow the old account once the request is sent + # @param [Account] source_account From which to follow + # @param [Account] target_account Account to follow + # @param [Account] old_target_account Account to unfollow once the follow request has been sent to the new one + # @option [Boolean] bypass_locked Whether to immediately follow the new account even if it is locked + def call(source_account, target_account, old_target_account, bypass_locked: false) + @old_target_account = old_target_account + + follow = source_account.active_relationships.find_by(target_account: old_target_account) + reblogs = follow&.show_reblogs? + notify = follow&.notify? + languages = follow&.languages + + super(source_account, target_account, reblogs: reblogs, notify: notify, languages: languages, bypass_locked: bypass_locked, bypass_limit: true) + end + + private + + def request_follow! + follow_request = @source_account.request_follow!(@target_account, **follow_options.merge(rate_limit: @options[:with_rate_limit], bypass_limit: @options[:bypass_limit])) + + if @target_account.local? + LocalNotificationWorker.perform_async(@target_account.id, follow_request.id, follow_request.class.name, 'follow_request') + UnfollowService.new.call(@source_account, @old_target_account, skip_unmerge: true) + elsif @target_account.activitypub? + ActivityPub::MigratedFollowDeliveryWorker.perform_async(build_json(follow_request), @source_account.id, @target_account.inbox_url, @old_target_account.id) + end + + follow_request + end + + def direct_follow! + follow = super + UnfollowService.new.call(@source_account, @old_target_account, skip_unmerge: true) + follow + end +end diff --git a/app/services/import_service.rb b/app/services/import_service.rb index 2f48abc36..56f191c1f 100644 --- a/app/services/import_service.rb +++ b/app/services/import_service.rb @@ -67,7 +67,7 @@ class ImportService < BaseService def import_relationships!(action, undo_action, overwrite_scope, limit, extra_fields = {}) local_domain_suffix = "@#{Rails.configuration.x.local_domain}" - items = @data.take(limit).map { |row| [row['Account address']&.strip&.delete_suffix(local_domain_suffix), Hash[extra_fields.map { |key, field_settings| [key, row[field_settings[:header]]&.strip || field_settings[:default]] }]] }.reject { |(id, _)| id.blank? } + items = @data.take(limit).map { |row| [row['Account address']&.strip&.delete_suffix(local_domain_suffix), extra_fields.to_h { |key, field_settings| [key, row[field_settings[:header]]&.strip || field_settings[:default]] }] }.reject { |(id, _)| id.blank? } if @import.overwrite? presence_hash = items.each_with_object({}) { |(id, extra), mapping| mapping[id] = [true, extra] } @@ -114,13 +114,13 @@ class ImportService < BaseService status || ActivityPub::FetchRemoteStatusService.new.call(uri) rescue HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::UnexpectedResponseError nil - rescue StandardError => e + rescue => e Rails.logger.warn "Unexpected error when importing bookmark: #{e}" nil end account_ids = statuses.map(&:account_id) - preloaded_relations = relations_map_for_account(@account, account_ids) + preloaded_relations = @account.relations_map(account_ids, skip_blocking_and_muting: true) statuses.keep_if { |status| StatusPolicy.new(@account, status, preloaded_relations).show? } @@ -138,14 +138,4 @@ class ImportService < BaseService def import_data Paperclip.io_adapters.for(@import.data).read.force_encoding(Encoding::UTF_8) end - - def relations_map_for_account(account, account_ids) - { - blocking: {}, - blocked_by: Account.blocked_by_map(account_ids, account.id), - muting: {}, - following: Account.following_map(account_ids, account.id), - domain_blocking_by_domain: {}, - } - end end diff --git a/app/services/keys/claim_service.rb b/app/services/keys/claim_service.rb index ae9e24a24..ebce9cce7 100644 --- a/app/services/keys/claim_service.rb +++ b/app/services/keys/claim_service.rb @@ -9,10 +9,10 @@ class Keys::ClaimService < BaseService def initialize(account, device_id, key_attributes = {}) super( - account: account, + account: account, device_id: device_id, - key_id: key_attributes[:key_id], - key: key_attributes[:key], + key_id: key_attributes[:key_id], + key: key_attributes[:key], signature: key_attributes[:signature], ) end @@ -58,7 +58,7 @@ class Keys::ClaimService < BaseService @result = Result.new(@target_account, @device_id, key_id: json['id'], key: json['publicKeyBase64'], signature: json.dig('signature', 'signatureValue')) rescue HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error => e - Rails.logger.debug "Claiming one-time key for #{@target_account.acct}:#{@device_id} failed: #{e}" + Rails.logger.debug { "Claiming one-time key for #{@target_account.acct}:#{@device_id} failed: #{e}" } nil end diff --git a/app/services/keys/query_service.rb b/app/services/keys/query_service.rb index ac3388bdc..14c9d9205 100644 --- a/app/services/keys/query_service.rb +++ b/app/services/keys/query_service.rb @@ -23,9 +23,9 @@ class Keys::QueryService < BaseService def initialize(attributes = {}) super( - device_id: attributes[:device_id], - name: attributes[:name], - identity_key: attributes[:identity_key], + device_id: attributes[:device_id], + name: attributes[:name], + identity_key: attributes[:identity_key], fingerprint_key: attributes[:fingerprint_key], ) @claim_url = attributes[:claim_url] @@ -73,7 +73,7 @@ class Keys::QueryService < BaseService Device.new(device_id: device['id'], name: device['name'], identity_key: device.dig('identityKey', 'publicKeyBase64'), fingerprint_key: device.dig('fingerprintKey', 'publicKeyBase64'), claim_url: device['claim']) end rescue HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error => e - Rails.logger.debug "Querying devices for #{@account.acct} failed: #{e}" + Rails.logger.debug { "Querying devices for #{@account.acct} failed: #{e}" } nil end end diff --git a/app/services/notify_service.rb b/app/services/notify_service.rb index c7454fc60..069f370cf 100644 --- a/app/services/notify_service.rb +++ b/app/services/notify_service.rb @@ -3,6 +3,12 @@ class NotifyService < BaseService include Redisable + NON_EMAIL_TYPES = %i( + admin.report + admin.sign_up + update + ).freeze + def call(recipient, type, activity) @recipient = recipient @activity = activity @@ -31,15 +37,16 @@ class NotifyService < BaseService def following_sender? return @following_sender if defined?(@following_sender) + @following_sender = @recipient.following?(@notification.from_account) || @recipient.requested?(@notification.from_account) end def optional_non_follower? - @recipient.user.settings.interactions['must_be_follower'] && !@notification.from_account.following?(@recipient) + @recipient.user.settings['interactions.must_be_follower'] && !@notification.from_account.following?(@recipient) end def optional_non_following? - @recipient.user.settings.interactions['must_be_following'] && !following_sender? + @recipient.user.settings['interactions.must_be_following'] && !following_sender? end def message? @@ -81,7 +88,7 @@ class NotifyService < BaseService def optional_non_following_and_direct? direct_message? && - @recipient.user.settings.interactions['must_be_following_dm'] && + @recipient.user.settings['interactions.must_be_following_dm'] && !following_sender? && !response_to_recipient? end @@ -170,6 +177,6 @@ class NotifyService < BaseService end def send_email_for_notification_type? - @recipient.user.settings.notification_emails[@notification.type.to_s] + NON_EMAIL_TYPES.exclude?(@notification.type) && @recipient.user.settings["notification_emails.#{@notification.type}"] end end diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index bcda001f5..74ec47a33 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -6,6 +6,15 @@ class PostStatusService < BaseService MIN_SCHEDULE_OFFSET = 5.minutes.freeze + class UnexpectedMentionsError < StandardError + attr_reader :accounts + + def initialize(message, accounts) + super(message) + @accounts = accounts + end + end + # Post a text status update, fetch and notify remote users mentioned # @param [Account] account Account from which to post # @param [Hash] options @@ -21,6 +30,7 @@ class PostStatusService < BaseService # @option [Doorkeeper::Application] :application # @option [String] :idempotency Optional idempotency key # @option [Boolean] :with_rate_limit + # @option [Enumerable] :allowed_mentions Optional array of expected mentioned account IDs, raises `UnexpectedMentionsError` if unexpected accounts end up in mentions # @return [Status] def call(account, options = {}) @account = account @@ -51,17 +61,22 @@ class PostStatusService < BaseService private - def preprocess_attributes! - if @text.blank? && @options[:spoiler_text].present? - @text = '.' - if @media&.find(&:video?) || @media&.find(&:gifv?) - @text = '📹' - elsif @media&.find(&:audio?) - @text = '🎵' - elsif @media&.find(&:image?) - @text = '🖼' - end + def fill_blank_text! + return unless @text.blank? && @options[:spoiler_text].present? + + if @media&.any?(&:video?) || @media&.any?(&:gifv?) + @text = '📹' + elsif @media&.any?(&:audio?) + @text = '🎵' + elsif @media&.any?(&:image?) + @text = '🖼' + else + @text = '.' end + end + + def preprocess_attributes! + fill_blank_text! @sensitive = (@options[:sensitive].nil? ? @account.user&.setting_default_sensitive : @options[:sensitive]) || @options[:spoiler_text].present? @visibility = @options[:visibility] || @account.user&.setting_default_privacy @visibility = :unlisted if @visibility&.to_sym == :public && @account.silenced? @@ -72,14 +87,28 @@ class PostStatusService < BaseService end def process_status! + @status = @account.statuses.new(status_attributes) + process_mentions_service.call(@status, save_records: false) + safeguard_mentions!(@status) + # The following transaction block is needed to wrap the UPDATEs to # the media attachments when the status is created - ApplicationRecord.transaction do - @status = @account.statuses.create!(status_attributes) + @status.save! end end + def safeguard_mentions!(status) + return if @options[:allowed_mentions].nil? + + expected_account_ids = @options[:allowed_mentions].map(&:to_i) + + unexpected_accounts = status.mentions.map(&:account).to_a.reject { |mentioned_account| expected_account_ids.include?(mentioned_account.id) } + return if unexpected_accounts.empty? + + raise UnexpectedMentionsError.new('Post would be sent to unexpected accounts', unexpected_accounts) + end + def schedule_status! status_for_validation = @account.statuses.build(status_attributes) @@ -102,7 +131,6 @@ class PostStatusService < BaseService def postprocess_status! process_hashtags_service.call(@status) - process_mentions_service.call(@status) Trends.tags.register(@status) LinkCrawlWorker.perform_async(@status.id) DistributionWorker.perform_async(@status.id) @@ -162,8 +190,10 @@ class PostStatusService < BaseService def bump_potential_friendship! return if !@status.reply? || @account.id == @status.in_reply_to_account_id + ActivityTracker.increment('activity:interactions') return if @account.following?(@status.in_reply_to_account_id) + PotentialFriendshipTracker.record(@account.id, @status.in_reply_to_account_id, :reply) end diff --git a/app/services/process_mentions_service.rb b/app/services/process_mentions_service.rb index b117db8c2..b3b279147 100644 --- a/app/services/process_mentions_service.rb +++ b/app/services/process_mentions_service.rb @@ -3,12 +3,13 @@ class ProcessMentionsService < BaseService include Payloadable - # Scan status for mentions and fetch remote mentioned users, create - # local mention pointers, send Salmon notifications to mentioned - # remote users + # Scan status for mentions and fetch remote mentioned users, + # and create local mention pointers # @param [Status] status - def call(status) + # @param [Boolean] save_records Whether to save records in database + def call(status, save_records: true) @status = status + @save_records = save_records return unless @status.local? @@ -27,13 +28,11 @@ class ProcessMentionsService < BaseService @status.text = @status.text.gsub(Account::MENTION_RE) do |match| username, domain = Regexp.last_match(1).split('@') - domain = begin - if TagManager.instance.local_domain?(domain) - nil - else - TagManager.instance.normalize_domain(domain) - end - end + domain = if TagManager.instance.local_domain?(domain) + nil + else + TagManager.instance.normalize_domain(domain) + end mentioned_account = Account.find_remote(username, domain) @@ -55,14 +54,15 @@ class ProcessMentionsService < BaseService next match if mention_undeliverable?(mentioned_account) || mentioned_account&.suspended? mention = @previous_mentions.find { |x| x.account_id == mentioned_account.id } - mention ||= mentioned_account.mentions.new(status: @status) + mention ||= @current_mentions.find { |x| x.account_id == mentioned_account.id } + mention ||= @status.mentions.new(account: mentioned_account) @current_mentions << mention "@#{mentioned_account.acct}" end - @status.save! + @status.save! if @save_records end def assign_mentions! @@ -73,11 +73,12 @@ class ProcessMentionsService < BaseService mentioned_account_ids = @current_mentions.map(&:account_id) blocked_account_ids = Set.new(@status.account.block_relationships.where(target_account_id: mentioned_account_ids).pluck(:target_account_id)) - @current_mentions.select! { |mention| !(blocked_account_ids.include?(mention.account_id) || blocked_domains.include?(mention.account.domain)) } + dropped_mentions, @current_mentions = @current_mentions.partition { |mention| blocked_account_ids.include?(mention.account_id) || blocked_domains.include?(mention.account.domain) } + dropped_mentions.each(&:destroy) end @current_mentions.each do |mention| - mention.save if mention.new_record? + mention.save if mention.new_record? && @save_records end # If previous mentions are no longer contained in the text, convert them diff --git a/app/services/reblog_service.rb b/app/services/reblog_service.rb index 97f9f8e92..b73669f9d 100644 --- a/app/services/reblog_service.rb +++ b/app/services/reblog_service.rb @@ -20,13 +20,11 @@ class ReblogService < BaseService return reblog unless reblog.nil? - visibility = begin - if reblogged_status.hidden? - reblogged_status.visibility - else - options[:visibility] || account.user&.setting_default_privacy - end - end + visibility = if reblogged_status.hidden? + reblogged_status.visibility + else + options[:visibility] || account.user&.setting_default_privacy + end reblog = account.statuses.create!(reblog: reblogged_status, text: '', visibility: visibility, rate_limit: options[:with_rate_limit]) diff --git a/app/services/remove_domains_from_followers_service.rb b/app/services/remove_domains_from_followers_service.rb new file mode 100644 index 000000000..d76763409 --- /dev/null +++ b/app/services/remove_domains_from_followers_service.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +class RemoveDomainsFromFollowersService < BaseService + include Payloadable + + def call(source_account, target_domains) + source_account.passive_relationships.where(account_id: Account.where(domain: target_domains)).find_each do |follow| + follow.destroy + + create_notification(follow) if source_account.local? && !follow.account.local? && follow.account.activitypub? + end + end + + private + + def create_notification(follow) + ActivityPub::DeliveryWorker.perform_async(build_json(follow), follow.target_account_id, follow.account.inbox_url) + end + + def build_json(follow) + Oj.dump(serialize_payload(follow, ActivityPub::RejectFollowSerializer)) + end +end diff --git a/app/services/remove_from_followers_service.rb b/app/services/remove_from_followers_service.rb index 3dac5467f..007d5b1fd 100644 --- a/app/services/remove_from_followers_service.rb +++ b/app/services/remove_from_followers_service.rb @@ -7,9 +7,7 @@ class RemoveFromFollowersService < BaseService source_account.passive_relationships.where(account_id: target_accounts).find_each do |follow| follow.destroy - if source_account.local? && !follow.account.local? && follow.account.activitypub? - create_notification(follow) - end + create_notification(follow) if source_account.local? && !follow.account.local? && follow.account.activitypub? end end diff --git a/app/services/remove_status_service.rb b/app/services/remove_status_service.rb index 58bf1dcb7..4f98ccea7 100644 --- a/app/services/remove_status_service.rb +++ b/app/services/remove_status_service.rb @@ -90,7 +90,7 @@ class RemoveStatusService < BaseService status_reach_finder = StatusReachFinder.new(@status, unsafe: true) - ActivityPub::DeliveryWorker.push_bulk(status_reach_finder.inboxes) do |inbox_url| + ActivityPub::DeliveryWorker.push_bulk(status_reach_finder.inboxes, limit: 1_000) do |inbox_url| [signed_activity_json, @account.id, inbox_url] end end diff --git a/app/services/resolve_account_service.rb b/app/services/resolve_account_service.rb index d8b81a7b9..abe1534a5 100644 --- a/app/services/resolve_account_service.rb +++ b/app/services/resolve_account_service.rb @@ -54,7 +54,7 @@ class ResolveAccountService < BaseService fetch_account! rescue Webfinger::Error => e - Rails.logger.debug "Webfinger query for #{@uri} failed: #{e}" + Rails.logger.debug { "Webfinger query for #{@uri} failed: #{e}" } raise unless @options[:suppress_errors] end @@ -71,13 +71,11 @@ class ResolveAccountService < BaseService @username, @domain = uri.strip.gsub(/\A@/, '').split('@') end - @domain = begin - if TagManager.instance.local_domain?(@domain) - nil - else - TagManager.instance.normalize_domain(@domain) - end - end + @domain = if TagManager.instance.local_domain?(@domain) + nil + else + TagManager.instance.normalize_domain(@domain) + end @uri = [@username, @domain].compact.join('@') end @@ -96,9 +94,7 @@ class ResolveAccountService < BaseService @webfinger = webfinger!("acct:#{confirmed_username}@#{confirmed_domain}") @username, @domain = split_acct(@webfinger.subject) - unless confirmed_username.casecmp(@username).zero? && confirmed_domain.casecmp(@domain).zero? - raise Webfinger::RedirectError, "Too many webfinger redirects for URI #{uri} (stopped at #{@username}@#{@domain})" - end + raise Webfinger::RedirectError, "Too many webfinger redirects for URI #{uri} (stopped at #{@username}@#{@domain})" unless confirmed_username.casecmp(@username).zero? && confirmed_domain.casecmp(@domain).zero? rescue Webfinger::GoneError @gone = true end diff --git a/app/services/resolve_url_service.rb b/app/services/resolve_url_service.rb index 52f35daf3..d8e795f3b 100644 --- a/app/services/resolve_url_service.rb +++ b/app/services/resolve_url_service.rb @@ -25,7 +25,7 @@ class ResolveURLService < BaseService if equals_or_includes_any?(type, ActivityPub::FetchRemoteActorService::SUPPORTED_TYPES) ActivityPub::FetchRemoteActorService.new.call(resource_url, prefetched_body: body) elsif equals_or_includes_any?(type, ActivityPub::Activity::Create::SUPPORTED_TYPES + ActivityPub::Activity::Create::CONVERTED_TYPES) - status = FetchRemoteStatusService.new.call(resource_url, body) + status = FetchRemoteStatusService.new.call(resource_url, prefetched_body: body) authorize_with @on_behalf_of, status, :show? unless status.nil? status end diff --git a/app/services/search_service.rb b/app/services/search_service.rb index 1a76cbb38..b1ce5453f 100644 --- a/app/services/search_service.rb +++ b/app/services/search_service.rb @@ -37,9 +37,7 @@ class SearchService < BaseService def perform_statuses_search! definition = parsed_query.apply(StatusesIndex.filter(term: { searchable_by: @account.id })) - if @options[:account_id].present? - definition = definition.filter(term: { account_id: @options[:account_id] }) - end + definition = definition.filter(term: { account_id: @options[:account_id] }) if @options[:account_id].present? if @options[:min_id].present? || @options[:max_id].present? range = {} @@ -51,7 +49,7 @@ class SearchService < BaseService results = definition.limit(@limit).offset(@offset).objects.compact account_ids = results.map(&:account_id) account_domains = results.map(&:account_domain) - preloaded_relations = relations_map_for_account(@account, account_ids, account_domains) + preloaded_relations = @account.relations_map(account_ids, account_domains) results.reject { |status| StatusFilter.new(status, @account, preloaded_relations).filtered? } rescue Faraday::ConnectionFailed, Parslet::ParseFailed @@ -113,16 +111,6 @@ class SearchService < BaseService @options[:type].blank? || @options[:type] == 'statuses' end - def relations_map_for_account(account, account_ids, domains) - { - blocking: Account.blocking_map(account_ids, account.id), - blocked_by: Account.blocked_by_map(account_ids, account.id), - muting: Account.muting_map(account_ids, account.id), - following: Account.following_map(account_ids, account.id), - domain_blocking_by_domain: Account.domain_blocking_map_by_domain(domains, account.id), - } - end - def parsed_query SearchQueryTransformer.new.apply(SearchQueryParser.new.parse(@query)) end diff --git a/app/services/suspend_account_service.rb b/app/services/suspend_account_service.rb index 211544fea..cfb3eb583 100644 --- a/app/services/suspend_account_service.rb +++ b/app/services/suspend_account_service.rb @@ -31,13 +31,13 @@ class SuspendAccountService < BaseService # counterpart to this operation, i.e. you can't then force a remote # account to re-follow you, so this part is not reversible. - follows = Follow.where(account: @account).to_a + Follow.where(account: @account).find_in_batches do |follows| + ActivityPub::DeliveryWorker.push_bulk(follows) do |follow| + [Oj.dump(serialize_payload(follow, ActivityPub::RejectFollowSerializer)), follow.target_account_id, @account.inbox_url] + end - ActivityPub::DeliveryWorker.push_bulk(follows) do |follow| - [Oj.dump(serialize_payload(follow, ActivityPub::RejectFollowSerializer)), follow.target_account_id, @account.inbox_url] + follows.each(&:destroy) end - - follows.each(&:destroy) end def distribute_update_actor! @@ -45,7 +45,7 @@ class SuspendAccountService < BaseService account_reach_finder = AccountReachFinder.new(@account) - ActivityPub::DeliveryWorker.push_bulk(account_reach_finder.inboxes) do |inbox_url| + ActivityPub::DeliveryWorker.push_bulk(account_reach_finder.inboxes, limit: 1_000) do |inbox_url| [signed_activity_json, @account.id, inbox_url] end end diff --git a/app/services/translate_status_service.rb b/app/services/translate_status_service.rb index 539a0d9db..796f13a0d 100644 --- a/app/services/translate_status_service.rb +++ b/app/services/translate_status_service.rb @@ -6,19 +6,29 @@ class TranslateStatusService < BaseService include FormattingHelper def call(status, target_language) - raise Mastodon::NotPermittedError unless status.public_visibility? || status.unlisted_visibility? - @status = status @content = status_content_format(@status) @target_language = target_language + raise Mastodon::NotPermittedError unless permitted? + Rails.cache.fetch("translations/#{@status.language}/#{@target_language}/#{content_hash}", expires_in: CACHE_TTL) { translation_backend.translate(@content, @status.language, @target_language) } end private def translation_backend - TranslationService.configured + @translation_backend ||= TranslationService.configured + end + + def permitted? + return false unless @status.distributable? && @status.content.present? && TranslationService.configured? + + languages[@status.language]&.include?(@target_language) + end + + def languages + Rails.cache.fetch('translation_service/languages', expires_in: 7.days, race_condition_ttl: 1.hour) { TranslationService.configured.languages } end def content_hash diff --git a/app/services/unsuspend_account_service.rb b/app/services/unsuspend_account_service.rb index 70667308e..d851a0f70 100644 --- a/app/services/unsuspend_account_service.rb +++ b/app/services/unsuspend_account_service.rb @@ -41,7 +41,7 @@ class UnsuspendAccountService < BaseService account_reach_finder = AccountReachFinder.new(@account) - ActivityPub::DeliveryWorker.push_bulk(account_reach_finder.inboxes) do |inbox_url| + ActivityPub::DeliveryWorker.push_bulk(account_reach_finder.inboxes, limit: 1_000) do |inbox_url| [signed_activity_json, @account.id, inbox_url] end end diff --git a/app/services/update_account_service.rb b/app/services/update_account_service.rb index 71976ab00..4604d71b2 100644 --- a/app/services/update_account_service.rb +++ b/app/services/update_account_service.rb @@ -22,7 +22,7 @@ class UpdateAccountService < BaseService def authorize_all_follow_requests(account) follow_requests = FollowRequest.where(target_account: account) follow_requests = follow_requests.preload(:account).select { |req| !req.account.silenced? } - AuthorizeFollowWorker.push_bulk(follow_requests) do |req| + AuthorizeFollowWorker.push_bulk(follow_requests, limit: 1_000) do |req| [req.account_id, req.target_account_id] end end diff --git a/app/services/update_status_service.rb b/app/services/update_status_service.rb index cc4ec670d..de6f1e6d1 100644 --- a/app/services/update_status_service.rb +++ b/app/services/update_status_service.rb @@ -10,6 +10,7 @@ class UpdateStatusService < BaseService # @param [Integer] account_id # @param [Hash] options # @option options [Array<Integer>] :media_ids + # @option options [Array<Hash>] :media_attributes # @option options [Hash] :poll # @option options [String] :text # @option options [String] :spoiler_text @@ -51,10 +52,18 @@ class UpdateStatusService < BaseService next_media_attachments = validate_media! added_media_attachments = next_media_attachments - previous_media_attachments + (@options[:media_attributes] || []).each do |attributes| + media = next_media_attachments.find { |attachment| attachment.id == attributes[:id].to_i } + next if media.nil? + + media.update!(attributes.slice(:thumbnail, :description, :focus)) + @media_attachments_changed ||= media.significantly_changed? + end + MediaAttachment.where(id: added_media_attachments.map(&:id)).update_all(status_id: @status.id) @status.ordered_media_attachment_ids = (@options[:media_ids] || []).map(&:to_i) & next_media_attachments.map(&:id) - @media_attachments_changed = previous_media_attachments.map(&:id) != @status.ordered_media_attachment_ids + @media_attachments_changed ||= previous_media_attachments.map(&:id) != @status.ordered_media_attachment_ids @status.media_attachments.reload end @@ -134,9 +143,9 @@ class UpdateStatusService < BaseService 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 + # expiration date, schedule a notification - return unless poll.present? && poll.expires_at.present? && poll.votes.exists? + return unless poll.present? && poll.expires_at.present? 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) diff --git a/app/services/verify_link_service.rb b/app/services/verify_link_service.rb index d049b52d1..707aeb4e0 100644 --- a/app/services/verify_link_service.rb +++ b/app/services/verify_link_service.rb @@ -10,8 +10,8 @@ class VerifyLinkService < BaseService return unless link_back_present? field.mark_verified! - rescue OpenSSL::SSL::SSLError, HTTP::Error, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError => e - Rails.logger.debug "Error fetching link #{@url}: #{e}" + rescue OpenSSL::SSL::SSLError, HTTP::Error, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError, IPAddr::AddressFamilyError => e + Rails.logger.debug { "Error fetching link #{@url}: #{e}" } nil end @@ -19,7 +19,7 @@ class VerifyLinkService < BaseService def perform_request! @body = Request.new(:get, @url).add_headers('Accept' => 'text/html').perform do |res| - res.code != 200 ? nil : res.body_with_limit + res.code == 200 ? res.body_with_limit : nil end end diff --git a/app/services/vote_service.rb b/app/services/vote_service.rb index 114ec285c..9ebf5a98d 100644 --- a/app/services/vote_service.rb +++ b/app/services/vote_service.rb @@ -44,11 +44,13 @@ class VoteService < BaseService def distribute_poll! return if @poll.hide_totals? + ActivityPub::DistributePollUpdateWorker.perform_in(3.minutes, @poll.status.id) end def queue_final_poll_check! return unless @poll.expires? + PollExpirationNotifyWorker.perform_at(@poll.expires_at + 5.minutes, @poll.id) end |