Merge tag 'v2.6.0rc1' into instance_only_statuses

This commit is contained in:
Renato "Lond" Cerqueira
2018-10-23 08:32:55 +02:00
570 changed files with 11506 additions and 5693 deletions

View File

@ -34,6 +34,7 @@ class ActivityPub::ProcessAccountService < BaseService
after_protocol_change! if protocol_changed?
after_key_change! if key_changed? && !@options[:signed_with_known_key]
check_featured_collection! if @account.featured_collection_url.present?
check_links! unless @account.fields.empty?
@account
rescue Oj::ParseError
@ -99,6 +100,10 @@ class ActivityPub::ProcessAccountService < BaseService
ActivityPub::SynchronizeFeaturedCollectionWorker.perform_async(@account.id)
end
def check_links!
VerifyAccountLinksWorker.perform_async(@account.id)
end
def actor_type
if @json['type'].is_a?(Array)
@json['type'].find { |type| ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES.include?(type) }

View File

@ -2,16 +2,43 @@
class AfterBlockService < BaseService
def call(account, target_account)
FeedManager.instance.clear_from_timeline(account, target_account)
clear_home_feed(account, target_account)
clear_notifications(account, target_account)
clear_conversations(account, target_account)
end
private
def clear_home_feed(account, target_account)
FeedManager.instance.clear_from_timeline(account, target_account)
end
def clear_conversations(account, target_account)
AccountConversation.where(account: account)
.where('? = ANY(participant_account_ids)', target_account.id)
.in_batches
.destroy_all
end
def clear_notifications(account, target_account)
Notification.where(account: account).joins(:follow).where(activity_type: 'Follow', follows: { account_id: target_account.id }).delete_all
Notification.where(account: account).joins(mention: :status).where(activity_type: 'Mention', statuses: { account_id: target_account.id }).delete_all
Notification.where(account: account).joins(:favourite).where(activity_type: 'Favourite', favourites: { account_id: target_account.id }).delete_all
Notification.where(account: account).joins(:status).where(activity_type: 'Status', statuses: { account_id: target_account.id }).delete_all
Notification.where(account: account)
.joins(:follow)
.where(activity_type: 'Follow', follows: { account_id: target_account.id })
.delete_all
Notification.where(account: account)
.joins(mention: :status)
.where(activity_type: 'Mention', statuses: { account_id: target_account.id })
.delete_all
Notification.where(account: account)
.joins(:favourite)
.where(activity_type: 'Favourite', favourites: { account_id: target_account.id })
.delete_all
Notification.where(account: account)
.joins(:status)
.where(activity_type: 'Status', statuses: { account_id: target_account.id })
.delete_all
end
end

View File

@ -12,7 +12,7 @@ class BatchedRemoveStatusService < BaseService
def call(statuses)
statuses = Status.where(id: statuses.map(&:id)).includes(:account, :stream_entry).flat_map { |status| [status] + status.reblogs.includes(:account, :stream_entry).to_a }
@mentions = statuses.map { |s| [s.id, s.mentions.includes(:account).to_a] }.to_h
@mentions = statuses.map { |s| [s.id, s.active_mentions.includes(:account).to_a] }.to_h
@tags = statuses.map { |s| [s.id, s.tags.pluck(:name)] }.to_h
@stream_entry_batches = []
@ -39,7 +39,6 @@ class BatchedRemoveStatusService < BaseService
# Cannot be batched
statuses.each do |status|
unpush_from_public_timelines(status)
unpush_from_direct_timelines(status) if status.direct_visibility?
batch_salmon_slaps(status) if status.local?
end
@ -96,16 +95,6 @@ class BatchedRemoveStatusService < BaseService
end
end
def unpush_from_direct_timelines(status)
payload = @json_payloads[status.id]
redis.pipelined do
@mentions[status.id].each do |mention|
redis.publish("timeline:direct:#{mention.account.id}", payload) if mention.account.local?
end
redis.publish("timeline:direct:#{status.account.id}", payload) if status.account.local?
end
end
def batch_salmon_slaps(status)
return if @mentions[status.id].empty?

View File

@ -6,14 +6,14 @@ class FanOutOnWriteService < BaseService
def call(status)
raise Mastodon::RaceConditionError if status.visibility.nil?
deliver_to_self(status) if status.account.local?
render_anonymous_payload(status)
if status.direct_visibility?
deliver_to_own_conversation(status)
elsif status.limited_visibility?
deliver_to_mentioned_followers(status)
deliver_to_direct_timelines(status)
else
deliver_to_self(status) if status.account.local?
deliver_to_followers(status)
deliver_to_lists(status)
end
@ -56,7 +56,7 @@ class FanOutOnWriteService < BaseService
end
def deliver_to_mentioned_followers(status)
Rails.logger.debug "Delivering status #{status.id} to mentioned followers"
Rails.logger.debug "Delivering status #{status.id} to limited followers"
status.mentions.includes(:account).each do |mention|
mentioned_account = mention.account
@ -93,12 +93,7 @@ class FanOutOnWriteService < BaseService
Redis.current.publish('timeline:public:local:media', @payload) if status.local?
end
def deliver_to_direct_timelines(status)
Rails.logger.debug "Delivering status #{status.id} to direct timelines"
status.mentions.includes(:account).each do |mention|
Redis.current.publish("timeline:direct:#{mention.account.id}", @payload) if mention.account.local?
end
Redis.current.publish("timeline:direct:#{status.account.id}", @payload) if status.account.local?
def deliver_to_own_conversation(status)
AccountConversation.add_status(status.account, status)
end
end

View File

@ -29,7 +29,7 @@ class FetchLinkCardService < BaseService
end
attach_card if @card&.persisted?
rescue HTTP::Error, Addressable::URI::InvalidURIError, Mastodon::LengthValidationError => e
rescue HTTP::Error, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError => e
Rails.logger.debug "Error fetching link #{@url}: #{e}"
nil
end
@ -87,34 +87,36 @@ class FetchLinkCardService < BaseService
end
def attempt_oembed
embed = FetchOEmbedService.new.call(@url, html: @html)
service = FetchOEmbedService.new
embed = service.call(@url, html: @html)
url = Addressable::URI.parse(service.endpoint_url)
return false if embed.nil?
@card.type = embed[:type]
@card.title = embed[:title] || ''
@card.author_name = embed[:author_name] || ''
@card.author_url = embed[:author_url] || ''
@card.author_url = embed[:author_url].present? ? (url + embed[:author_url]).to_s : ''
@card.provider_name = embed[:provider_name] || ''
@card.provider_url = embed[:provider_url] || ''
@card.provider_url = embed[:provider_url].present? ? (url + embed[:provider_url]).to_s : ''
@card.width = 0
@card.height = 0
case @card.type
when 'link'
@card.image_remote_url = embed[:thumbnail_url] if embed[:thumbnail_url].present?
@card.image_remote_url = (url + embed[:thumbnail_url]).to_s if embed[:thumbnail_url].present?
when 'photo'
return false if embed[:url].blank?
@card.embed_url = embed[:url]
@card.image_remote_url = embed[:url]
@card.embed_url = (url + embed[:url]).to_s
@card.image_remote_url = (url + embed[:url]).to_s
@card.width = embed[:width].presence || 0
@card.height = embed[:height].presence || 0
when 'video'
@card.width = embed[:width].presence || 0
@card.height = embed[:height].presence || 0
@card.html = Formatter.instance.sanitize(embed[:html], Sanitize::Config::MASTODON_OEMBED)
@card.image_remote_url = embed[:thumbnail_url] if embed[:thumbnail_url].present?
@card.image_remote_url = (url + embed[:thumbnail_url]).to_s if embed[:thumbnail_url].present?
when 'rich'
# Most providers rely on <script> tags, which is a no-no
return false
@ -146,7 +148,7 @@ class FetchLinkCardService < BaseService
@card.title = meta_property(page, 'og:title').presence || page.at_xpath('//title')&.content || ''
@card.description = meta_property(page, 'og:description').presence || meta_property(page, 'description') || ''
@card.image_remote_url = meta_property(page, 'og:image') if meta_property(page, 'og:image')
@card.image_remote_url = (Addressable::URI.parse(@url) + meta_property(page, 'og:image')).to_s if meta_property(page, 'og:image')
return if @card.title.blank? && @card.html.blank?

View File

@ -31,7 +31,7 @@ class FetchOEmbedService
return if @endpoint_url.blank?
@endpoint_url = Addressable::URI.parse(@endpoint_url).to_s
@endpoint_url = (Addressable::URI.parse(@url) + @endpoint_url).to_s
rescue Addressable::URI::InvalidURIError
@endpoint_url = nil
end

View File

@ -5,11 +5,13 @@ class MuteService < BaseService
return if account.id == target_account.id
mute = account.mute!(target_account, notifications: notifications)
if mute.hide_notifications?
BlockWorker.perform_async(account.id, target_account.id)
else
FeedManager.instance.clear_from_timeline(account, target_account)
MuteWorker.perform_async(account.id, target_account.id)
end
mute
end
end

View File

@ -8,9 +8,10 @@ class NotifyService < BaseService
return if recipient.user.nil? || blocked?
create_notification
push_notification if @notification.browserable?
send_email if email_enabled?
create_notification!
push_notification! if @notification.browserable?
push_to_conversation! if direct_message?
send_email! if email_enabled?
rescue ActiveRecord::RecordInvalid
return
end
@ -58,9 +59,14 @@ class NotifyService < BaseService
@notification.target_status.in_reply_to_account_id == @recipient.id && @notification.target_status.thread&.direct_visibility?
end
def from_staff?
@notification.from_account.local? && @notification.from_account.user.present? && @notification.from_account.user.staff?
end
def optional_non_following_and_direct?
direct_message? &&
@recipient.user.settings.interactions['must_be_following_dm'] &&
!from_staff? &&
!following_sender? &&
!response_to_recipient?
end
@ -100,18 +106,23 @@ class NotifyService < BaseService
end
end
def create_notification
def create_notification!
@notification.save!
end
def push_notification
def push_notification!
return if @notification.activity.nil?
Redis.current.publish("timeline:#{@recipient.id}", Oj.dump(event: :notification, payload: InlineRenderer.render(@notification, @recipient, :notification)))
send_push_notifications
send_push_notifications!
end
def send_push_notifications
def push_to_conversation!
return if @notification.activity.nil?
AccountConversation.add_status(@recipient, @notification.target_status)
end
def send_push_notifications!
subscriptions_ids = ::Web::PushSubscription.where(user_id: @recipient.user.id)
.select { |subscription| subscription.pushable?(@notification) }
.map(&:id)
@ -121,7 +132,7 @@ class NotifyService < BaseService
end
end
def send_email
def send_email!
return if @notification.activity.nil?
NotificationMailer.public_send(@notification.type, @recipient, @notification).deliver_later(wait: 2.minutes)
end

View File

@ -8,7 +8,7 @@ class RemoveStatusService < BaseService
@status = status
@account = status.account
@tags = status.tags.pluck(:name).to_a
@mentions = status.mentions.includes(:account).to_a
@mentions = status.active_mentions.includes(:account).to_a
@reblogs = status.reblogs.to_a
@stream_entry = status.stream_entry
@options = options
@ -21,7 +21,6 @@ class RemoveStatusService < BaseService
remove_from_hashtags
remove_from_public
remove_from_media if status.media_attachments.any?
remove_from_direct if status.direct_visibility?
@status.destroy!
@ -153,13 +152,6 @@ class RemoveStatusService < BaseService
Redis.current.publish('timeline:public:local:media', @payload) if @status.local?
end
def remove_from_direct
@mentions.each do |mention|
Redis.current.publish("timeline:direct:#{mention.account.id}", @payload) if mention.account.local?
end
Redis.current.publish("timeline:direct:#{@account.id}", @payload) if @account.local?
end
def redis
Redis.current
end

View File

@ -2,20 +2,26 @@
class UpdateAccountService < BaseService
def call(account, params, raise_error: false)
was_locked = account.locked
was_locked = account.locked
update_method = raise_error ? :update! : :update
account.send(update_method, params).tap do |ret|
next unless ret
authorize_all_follow_requests(account) if was_locked && !account.locked
check_links(account)
end
end
private
def authorize_all_follow_requests(account)
follow_requests = FollowRequest.where(target_account: account)
AuthorizeFollowWorker.push_bulk(follow_requests) do |req|
AuthorizeFollowWorker.push_bulk(FollowRequest.where(target_account: account).select(:account_id, :target_account_id)) do |req|
[req.account_id, req.target_account_id]
end
end
def check_links(account)
VerifyAccountLinksWorker.perform_async(account.id)
end
end

View File

@ -0,0 +1,48 @@
# frozen_string_literal: true
class VerifyLinkService < BaseService
def call(field)
@link_back = ActivityPub::TagManager.instance.url_for(field.account)
@url = field.value_for_verification
perform_request!
return unless link_back_present?
field.mark_verified!
field.account.save!
rescue HTTP::Error, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError => e
Rails.logger.debug "Error fetching link #{@url}: #{e}"
nil
end
private
def perform_request!
@body = Request.new(:get, @url).add_headers('Accept' => 'text/html').perform do |res|
res.code != 200 ? nil : res.body_with_limit
end
end
def link_back_present?
return false if @body.empty?
links = Nokogiri::HTML(@body).xpath('//a[contains(concat(" ", normalize-space(@rel), " "), " me ")]|//link[contains(concat(" ", normalize-space(@rel), " "), " me ")]')
if links.any? { |link| link['href'] == @link_back }
true
elsif links.empty?
false
else
link_redirects_back?(links.first['href'])
end
end
def link_redirects_back?(test_url)
redirect_to_url = Request.new(:head, test_url, follow: false).perform do |res|
res.headers['Location']
end
redirect_to_url == @link_back
end
end