Add stop-gap antispam code (#32981)

This commit is contained in:
Claire 2024-11-21 15:06:57 +01:00 committed by GitHub
parent 4517e18b79
commit dbddd40c1c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 53 additions and 0 deletions

45
app/lib/antispam.rb Normal file
View file

@ -0,0 +1,45 @@
# frozen_string_literal: true
class Antispam
include Redisable
ACCOUNT_AGE_EXEMPTION = 1.week.freeze
class SilentlyDrop < StandardError
attr_reader :status
def initialize(status)
super()
@status = status
status.created_at = Time.now.utc
status.id = Mastodon::Snowflake.id_at(status.created_at)
status.in_reply_to_account_id = status.thread&.account_id
status.delete # Make sure this is not persisted
end
end
def local_preflight_check!(status)
return unless spammy_texts.any? { |spammy_text| status.text.include?(spammy_text) }
return unless status.thread.present? && !status.thread.account.following?(status.account)
return unless status.account.created_at >= ACCOUNT_AGE_EXEMPTION.ago
report_if_needed!(status.account)
raise SilentlyDrop, status
end
private
def spammy_texts
redis.smembers('antispam:spammy_texts')
end
def report_if_needed!(account)
return if Report.unresolved.exists?(account: Account.representative, target_account: account)
Report.create!(account: Account.representative, target_account: account, category: :spam, comment: 'Account automatically reported for posting a banned URL')
end
end

View file

@ -36,6 +36,8 @@ class PostStatusService < BaseService
@text = @options[:text] || '' @text = @options[:text] || ''
@in_reply_to = @options[:thread] @in_reply_to = @options[:thread]
@antispam = Antispam.new
return idempotency_duplicate if idempotency_given? && idempotency_duplicate? return idempotency_duplicate if idempotency_given? && idempotency_duplicate?
validate_media! validate_media!
@ -55,6 +57,8 @@ class PostStatusService < BaseService
end end
@status @status
rescue Antispam::SilentlyDrop => e
e.status
end end
private private
@ -74,6 +78,7 @@ class PostStatusService < BaseService
@status = @account.statuses.new(status_attributes) @status = @account.statuses.new(status_attributes)
process_mentions_service.call(@status, save_records: false) process_mentions_service.call(@status, save_records: false)
safeguard_mentions!(@status) safeguard_mentions!(@status)
@antispam.local_preflight_check!(@status)
# The following transaction block is needed to wrap the UPDATEs to # The following transaction block is needed to wrap the UPDATEs to
# the media attachments when the status is created # the media attachments when the status is created
@ -95,6 +100,7 @@ class PostStatusService < BaseService
def schedule_status! def schedule_status!
status_for_validation = @account.statuses.build(status_attributes) status_for_validation = @account.statuses.build(status_attributes)
@antispam.local_preflight_check!(status_for_validation)
if status_for_validation.valid? if status_for_validation.valid?
# Marking the status as destroyed is necessary to prevent the status from being # Marking the status as destroyed is necessary to prevent the status from being
@ -111,6 +117,8 @@ class PostStatusService < BaseService
else else
raise ActiveRecord::RecordInvalid raise ActiveRecord::RecordInvalid
end end
rescue Antispam::SilentlyDrop
@status = @account.scheduled_status.new(scheduled_status_attributes).tap(&:delete)
end end
def postprocess_status! def postprocess_status!