diff options
Diffstat (limited to 'app/services')
21 files changed, 181 insertions, 43 deletions
diff --git a/app/services/account_search_service.rb b/app/services/account_search_service.rb new file mode 100644 index 000000000..f55439dcb --- /dev/null +++ b/app/services/account_search_service.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +class AccountSearchService < BaseService + def call(query, limit, resolve = false, account = nil) + return [] if query.blank? || query.start_with?('#') + + username, domain = query.gsub(/\A@/, '').split('@') + domain = nil if TagManager.instance.local_domain?(domain) + + if domain.nil? + exact_match = Account.find_local(username) + results = account.nil? ? Account.search_for(username, limit) : Account.advanced_search_for(username, account, limit) + else + exact_match = Account.find_remote(username, domain) + results = account.nil? ? Account.search_for("#{username} #{domain}", limit) : Account.advanced_search_for("#{username} #{domain}", account, limit) + end + + results = [exact_match] + results.reject { |a| a.id == exact_match.id } if exact_match + + if resolve && !exact_match && !domain.nil? + results = [FollowRemoteAccountService.new.call("#{username}@#{domain}")] + end + + results + end +end diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb index 71f6cbca1..402b84b2f 100644 --- a/app/services/fan_out_on_write_service.rb +++ b/app/services/fan_out_on_write_service.rb @@ -5,7 +5,12 @@ class FanOutOnWriteService < BaseService # @param [Status] status def call(status) deliver_to_self(status) if status.account.local? - deliver_to_followers(status) + + if status.direct_visibility? + deliver_to_mentioned_followers(status) + else + deliver_to_followers(status) + end return if status.account.silenced? || !status.public_visibility? || status.reblog? @@ -32,6 +37,16 @@ class FanOutOnWriteService < BaseService end end + def deliver_to_mentioned_followers(status) + Rails.logger.debug "Delivering status #{status.id} to mentioned followers" + + status.mentions.includes(:account).each do |mention| + mentioned_account = mention.account + next if !mentioned_account.local? || !mentioned_account.following?(status.account) || FeedManager.instance.filter?(:home, status, mentioned_account) + FeedManager.instance.push(:home, mentioned_account, status) + end + end + def deliver_to_hashtags(status) Rails.logger.debug "Delivering status #{status.id} to hashtags" diff --git a/app/services/favourite_service.rb b/app/services/favourite_service.rb index 824729ed6..5cc96403c 100644 --- a/app/services/favourite_service.rb +++ b/app/services/favourite_service.rb @@ -6,7 +6,7 @@ class FavouriteService < BaseService # @param [Status] status # @return [Favourite] def call(account, status) - raise Mastodon::NotPermitted unless status.permitted?(account) + raise Mastodon::NotPermittedError unless status.permitted?(account) favourite = Favourite.create!(account: account, status: status) @@ -22,10 +22,13 @@ class FavouriteService < BaseService private def build_xml(favourite) + description = "#{favourite.account.acct} favourited a status by #{favourite.status.account.acct}" + Nokogiri::XML::Builder.new do |xml| entry(xml, true) do unique_id xml, favourite.created_at, favourite.id, 'Favourite' - title xml, "#{favourite.account.acct} favourited a status by #{favourite.status.account.acct}" + title xml, description + content xml, description author(xml) do include_author xml, favourite.account diff --git a/app/services/fetch_atom_service.rb b/app/services/fetch_atom_service.rb index f7e9c150a..c3dad1eb9 100644 --- a/app/services/fetch_atom_service.rb +++ b/app/services/fetch_atom_service.rb @@ -47,6 +47,6 @@ class FetchAtomService < BaseService end def http_client - HTTP.timeout(:per_operation, write: 20, connect: 20, read: 50).follow + HTTP.timeout(:per_operation, write: 10, connect: 10, read: 10).follow end end diff --git a/app/services/fetch_remote_account_service.rb b/app/services/fetch_remote_account_service.rb index baefa3a86..6a6a696d6 100644 --- a/app/services/fetch_remote_account_service.rb +++ b/app/services/fetch_remote_account_service.rb @@ -1,8 +1,13 @@ # frozen_string_literal: true class FetchRemoteAccountService < BaseService - def call(url) - atom_url, body = FetchAtomService.new.call(url) + def call(url, prefetched_body = nil) + if prefetched_body.nil? + atom_url, body = FetchAtomService.new.call(url) + else + atom_url = url + body = prefetched_body + end return nil if atom_url.nil? process_atom(atom_url, body) diff --git a/app/services/fetch_remote_resource_service.rb b/app/services/fetch_remote_resource_service.rb new file mode 100644 index 000000000..2185ceb20 --- /dev/null +++ b/app/services/fetch_remote_resource_service.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class FetchRemoteResourceService < BaseService + def call(url) + atom_url, body = FetchAtomService.new.call(url) + + return nil if atom_url.nil? + + xml = Nokogiri::XML(body) + xml.encoding = 'utf-8' + + if xml.root.name == 'feed' + FetchRemoteAccountService.new.call(atom_url, body) + elsif xml.root.name == 'entry' + FetchRemoteStatusService.new.call(atom_url, body) + end + end +end diff --git a/app/services/fetch_remote_status_service.rb b/app/services/fetch_remote_status_service.rb index 7063231e4..e2d185723 100644 --- a/app/services/fetch_remote_status_service.rb +++ b/app/services/fetch_remote_status_service.rb @@ -1,8 +1,13 @@ # frozen_string_literal: true class FetchRemoteStatusService < BaseService - def call(url) - atom_url, body = FetchAtomService.new.call(url) + def call(url, prefetched_body = nil) + if prefetched_body.nil? + atom_url, body = FetchAtomService.new.call(url) + else + atom_url = url + body = prefetched_body + end return nil if atom_url.nil? process_atom(atom_url, body) diff --git a/app/services/follow_service.rb b/app/services/follow_service.rb index d67b1bf2d..17b3b2542 100644 --- a/app/services/follow_service.rb +++ b/app/services/follow_service.rb @@ -10,7 +10,7 @@ class FollowService < BaseService target_account = FollowRemoteAccountService.new.call(uri) raise ActiveRecord::RecordNotFound if target_account.nil? || target_account.id == source_account.id || target_account.suspended? - raise Mastodon::NotPermitted if target_account.blocking?(source_account) || source_account.blocking?(target_account) + raise Mastodon::NotPermittedError if target_account.blocking?(source_account) || source_account.blocking?(target_account) if target_account.locked? request_follow(source_account, target_account) @@ -55,10 +55,13 @@ class FollowService < BaseService end def build_follow_request_xml(follow_request) + description = "#{follow_request.account.acct} requested to follow #{follow_request.target_account.acct}" + Nokogiri::XML::Builder.new do |xml| entry(xml, true) do unique_id xml, follow_request.created_at, follow_request.id, 'FollowRequest' - title xml, "#{follow_request.account.acct} requested to follow #{follow_request.target_account.acct}" + title xml, description + content xml, description author(xml) do include_author xml, follow_request.account @@ -75,10 +78,13 @@ class FollowService < BaseService end def build_follow_xml(follow) + description = "#{follow.account.acct} started following #{follow.target_account.acct}" + Nokogiri::XML::Builder.new do |xml| entry(xml, true) do unique_id xml, follow.created_at, follow.id, 'Follow' - title xml, "#{follow.account.acct} started following #{follow.target_account.acct}" + title xml, description + content xml, description author(xml) do include_author xml, follow.account diff --git a/app/services/mute_service.rb b/app/services/mute_service.rb new file mode 100644 index 000000000..0050cfc8d --- /dev/null +++ b/app/services/mute_service.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +class MuteService < BaseService + def call(account, target_account) + return if account.id == target_account.id + clear_home_timeline(account, target_account) + account.mute!(target_account) + end + + private + + def clear_home_timeline(account, target_account) + home_key = FeedManager.instance.key(:home, account.id) + + target_account.statuses.select('id').find_each do |status| + redis.zrem(home_key, status.id) + end + end + + def redis + Redis.current + end +end diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index 979941c84..b8179f7dc 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -13,6 +13,7 @@ class PostStatusService < BaseService # @option [Doorkeeper::Application] :application # @return [Status] def call(account, text, in_reply_to = nil, options = {}) + media = validate_media!(options[:media_ids]) status = account.statuses.create!(text: text, thread: in_reply_to, sensitive: options[:sensitive], @@ -20,7 +21,7 @@ class PostStatusService < BaseService visibility: options[:visibility], application: options[:application]) - attach_media(status, options[:media_ids]) + attach_media(status, media) process_mentions_service.call(status) process_hashtags_service.call(status) @@ -33,10 +34,20 @@ class PostStatusService < BaseService private - def attach_media(status, media_ids) + def validate_media!(media_ids) return if media_ids.nil? || !media_ids.is_a?(Enumerable) + raise Mastodon::ValidationError, 'Cannot attach more than 4 files' if media_ids.size > 4 + media = MediaAttachment.where(status_id: nil).where(id: media_ids.take(4).map(&:to_i)) + + raise Mastodon::ValidationError, 'Cannot attach a video to a toot that already contains images' if media.size > 1 && media.find(&:video?) + + media + end + + def attach_media(status, media) + return if media.nil? media.update(status_id: status.id) end diff --git a/app/services/precompute_feed_service.rb b/app/services/precompute_feed_service.rb index 54d11b631..e1ec56e8d 100644 --- a/app/services/precompute_feed_service.rb +++ b/app/services/precompute_feed_service.rb @@ -4,10 +4,10 @@ class PrecomputeFeedService < BaseService # Fill up a user's home/mentions feed from DB and return a subset # @param [Symbol] type :home or :mentions # @param [Account] account - def call(type, account) - Status.send("as_#{type}_timeline", account).limit(FeedManager::MAX_ITEMS).each do |status| - next if FeedManager.instance.filter?(type, status, account) - redis.zadd(FeedManager.instance.key(type, account.id), status.id, status.reblog? ? status.reblog_of_id : status.id) + def call(_, account) + Status.as_home_timeline(account).limit(FeedManager::MAX_ITEMS).each do |status| + next if status.direct_visibility? || FeedManager.instance.filter?(:home, status, account) + redis.zadd(FeedManager.instance.key(:home, account.id), status.id, status.reblog? ? status.reblog_of_id : status.id) end end diff --git a/app/services/process_feed_service.rb b/app/services/process_feed_service.rb index f0a62aa14..69911abc5 100644 --- a/app/services/process_feed_service.rb +++ b/app/services/process_feed_service.rb @@ -61,12 +61,25 @@ class ProcessFeedService < BaseService status.save! - NotifyService.new.call(status.reblog.account, status) if status.reblog? && status.reblog.account.local? + notify_about_mentions!(status) unless status.reblog? + notify_about_reblog!(status) if status.reblog? && status.reblog.account.local? Rails.logger.debug "Queuing remote status #{status.id} (#{id}) for distribution" DistributionWorker.perform_async(status.id) status end + def notify_about_mentions!(status) + status.mentions.includes(:account).each do |mention| + mentioned_account = mention.account + next unless mentioned_account.local? + NotifyService.new.call(mentioned_account, mention) + end + end + + def notify_about_reblog!(status) + NotifyService.new.call(status.reblog.account, status) + end + def delete_status Rails.logger.debug "Deleting remote status #{id}" status = Status.find_by(uri: id) @@ -159,10 +172,7 @@ class ProcessFeedService < BaseService next if mentioned_account.nil? || processed_account_ids.include?(mentioned_account.id) - mention = mentioned_account.mentions.where(status: parent).first_or_create(status: parent) - - # Notify local user - NotifyService.new.call(mentioned_account, mention) if mentioned_account.local? + mentioned_account.mentions.where(status: parent).first_or_create(status: parent) # So we can skip duplicate mentions processed_account_ids << mentioned_account.id @@ -181,6 +191,9 @@ class ProcessFeedService < BaseService next unless link['href'] media = MediaAttachment.where(status: parent, remote_url: link['href']).first_or_initialize(account: parent.account, status: parent, remote_url: link['href']) + parsed_url = URI.parse(link['href']) + + next if !%w(http https).include?(parsed_url.scheme) || parsed_url.host.empty? begin media.file_remote_url = link['href'] diff --git a/app/services/process_interaction_service.rb b/app/services/process_interaction_service.rb index c74ff9e22..d5f7b4b3c 100644 --- a/app/services/process_interaction_service.rb +++ b/app/services/process_interaction_service.rb @@ -64,7 +64,7 @@ class ProcessInteractionService < BaseService end def mentions_account?(xml, account) - xml.xpath('/xmlns:entry/xmlns:link[@rel="mentioned"]', xmlns: TagManager::XMLNS).each { |mention_link| return true if mention_link.attribute('href').value == TagManager.instance.url_for(account) } + xml.xpath('/xmlns:entry/xmlns:link[@rel="mentioned"]', xmlns: TagManager::XMLNS).each { |mention_link| return true if [TagManager.instance.uri_for(account), TagManager.instance.url_for(account)].include?(mention_link.attribute('href').value) } false end diff --git a/app/services/process_mentions_service.rb b/app/services/process_mentions_service.rb index d3d3af8af..aa0a4d71b 100644 --- a/app/services/process_mentions_service.rb +++ b/app/services/process_mentions_service.rb @@ -27,7 +27,7 @@ class ProcessMentionsService < BaseService mentioned_account.mentions.where(status: status).first_or_create(status: status) end - status.mentions.each do |mention| + status.mentions.includes(:account).each do |mention| mentioned_account = mention.account if mentioned_account.local? diff --git a/app/services/reblog_service.rb b/app/services/reblog_service.rb index 7a52f041f..11446ce28 100644 --- a/app/services/reblog_service.rb +++ b/app/services/reblog_service.rb @@ -10,7 +10,7 @@ class ReblogService < BaseService def call(account, reblogged_status) reblogged_status = reblogged_status.reblog if reblogged_status.reblog? - raise Mastodon::NotPermitted if reblogged_status.private_visibility? || !reblogged_status.permitted?(account) + raise Mastodon::NotPermittedError if reblogged_status.direct_visibility? || reblogged_status.private_visibility? || !reblogged_status.permitted?(account) reblog = account.statuses.create!(reblog: reblogged_status, text: '') diff --git a/app/services/remove_status_service.rb b/app/services/remove_status_service.rb index 73b545f17..cf1f432e4 100644 --- a/app/services/remove_status_service.rb +++ b/app/services/remove_status_service.rb @@ -59,7 +59,7 @@ class RemoveStatusService < BaseService end def unpush(type, receiver, status) - if status.reblog? + if status.reblog? && !redis.zscore(FeedManager.instance.key(type, receiver.id), status.reblog_of_id).nil? redis.zadd(FeedManager.instance.key(type, receiver.id), status.reblog_of_id, status.reblog_of_id) else redis.zremrangebyscore(FeedManager.instance.key(type, receiver.id), status.id, status.id) diff --git a/app/services/search_service.rb b/app/services/search_service.rb index 04de8a134..e9745010b 100644 --- a/app/services/search_service.rb +++ b/app/services/search_service.rb @@ -1,24 +1,19 @@ # frozen_string_literal: true class SearchService < BaseService - def call(query, limit, resolve = false) - return if query.blank? || query.start_with?('#') + def call(query, limit, resolve = false, account = nil) + results = { accounts: [], hashtags: [], statuses: [] } - username, domain = query.gsub(/\A@/, '').split('@') + return results if query.blank? - if domain.nil? - exact_match = Account.find_local(username) - results = Account.search_for(username) - else - exact_match = Account.find_remote(username, domain) - results = Account.search_for("#{username} #{domain}") - end + if query =~ /\Ahttps?:\/\// + resource = FetchRemoteResourceService.new.call(query) - results = results.limit(limit).to_a - results = [exact_match] + results.reject { |a| a.id == exact_match.id } if exact_match - - if resolve && !exact_match && !domain.nil? - results = [FollowRemoteAccountService.new.call("#{username}@#{domain}")] + results[:accounts] << resource if resource.is_a?(Account) + results[:statuses] << resource if resource.is_a?(Status) + else + results[:accounts] = AccountSearchService.new.call(query, limit, resolve, account) + results[:hashtags] = Tag.search_for(query.gsub(/\A#/, ''), limit) unless query.start_with?('@') end results diff --git a/app/services/unfavourite_service.rb b/app/services/unfavourite_service.rb index 1d3e6f06d..5f0ba4254 100644 --- a/app/services/unfavourite_service.rb +++ b/app/services/unfavourite_service.rb @@ -13,10 +13,13 @@ class UnfavouriteService < BaseService private def build_xml(favourite) + description = "#{favourite.account.acct} no longer favourites a status by #{favourite.status.account.acct}" + Nokogiri::XML::Builder.new do |xml| entry(xml, true) do unique_id xml, Time.now.utc, favourite.id, 'Favourite' - title xml, "#{favourite.account.acct} no longer favourites a status by #{favourite.status.account.acct}" + title xml, description + content xml, description author(xml) do include_author xml, favourite.account diff --git a/app/services/unfollow_service.rb b/app/services/unfollow_service.rb index 07f9b93dd..3440da364 100644 --- a/app/services/unfollow_service.rb +++ b/app/services/unfollow_service.rb @@ -13,10 +13,13 @@ class UnfollowService < BaseService private def build_xml(follow) + description = "#{follow.account.acct} is no longer following #{follow.target_account.acct}" + Nokogiri::XML::Builder.new do |xml| entry(xml, true) do unique_id xml, Time.now.utc, follow.id, 'Follow' - title xml, "#{follow.account.acct} is no longer following #{follow.target_account.acct}" + title xml, description + content xml, description author(xml) do include_author xml, follow.account diff --git a/app/services/unmute_service.rb b/app/services/unmute_service.rb new file mode 100644 index 000000000..6aeea358f --- /dev/null +++ b/app/services/unmute_service.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class UnmuteService < BaseService + def call(account, target_account) + return unless account.muting?(target_account) + + account.unmute!(target_account) + + MergeWorker.perform_async(target_account.id, account.id) if account.following?(target_account) + end +end diff --git a/app/services/update_remote_profile_service.rb b/app/services/update_remote_profile_service.rb index dc315db19..74baa1cc5 100644 --- a/app/services/update_remote_profile_service.rb +++ b/app/services/update_remote_profile_service.rb @@ -14,6 +14,7 @@ class UpdateRemoteProfileService < BaseService unless account.suspended? || DomainBlock.find_by(domain: account.domain)&.reject_media? account.avatar_remote_url = author_xml.at_xpath('./xmlns:link[@rel="avatar"]', xmlns: TagManager::XMLNS)['href'] unless author_xml.at_xpath('./xmlns:link[@rel="avatar"]', xmlns: TagManager::XMLNS).nil? || author_xml.at_xpath('./xmlns:link[@rel="avatar"]', xmlns: TagManager::XMLNS)['href'].blank? + account.header_remote_url = author_xml.at_xpath('./xmlns:link[@rel="header"]', xmlns: TagManager::XMLNS)['href'] unless author_xml.at_xpath('./xmlns:link[@rel="header"]', xmlns: TagManager::XMLNS).nil? || author_xml.at_xpath('./xmlns:link[@rel="header"]', xmlns: TagManager::XMLNS)['href'].blank? end end |