mastodon/app/lib/antispam.rb

92 lines
2.1 KiB
Ruby

# frozen_string_literal: true
class Antispam
include Redisable
ACCOUNT_AGE_EXEMPTION = 1.week.freeze
class DummyStatus < SimpleDelegator
def self.model_name
Mention.model_name
end
def active_mentions
# Don't use the scope but the in-memory array
mentions.filter { |mention| !mention.silent? }
end
end
class SilentlyDrop < StandardError
attr_reader :status
def initialize(status)
super()
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
@status = DummyStatus.new(status)
end
end
def initialize(status)
@status = status
end
def local_preflight_check!
return unless considered_spam?
report_if_needed!
raise SilentlyDrop, @status
end
private
def considered_spam?
(all_time_suspicious? || recent_suspicious?) && suspicious_reply_or_mention?
end
def all_time_suspicious?
all_time_spammy_texts.any? { |spammy_text| status_text.include?(spammy_text) }
end
def recent_suspicious?
@status.account.created_at >= ACCOUNT_AGE_EXEMPTION.ago && spammy_texts.any? { |spammy_text| status_text.include?(spammy_text) }
end
def spammy_texts
redis.smembers('antispam:spammy_texts')
end
def all_time_spammy_texts
redis.smembers('antispam:all_time_spammy_texts')
end
def suspicious_reply_or_mention?
account_ids = ([@status.in_reply_to_account_id] + @status.mentions.map(&:account_id)).uniq
!Follow.exists?(account_id: account_ids, target_account_id: @status.account.id)
end
def report_if_needed!
return if system_reports.unresolved.exists?(target_account: @status.account)
system_reports.create!(
category: :spam,
comment: 'Account automatically reported for posting a banned URL',
target_account: @status.account
)
end
def system_reports
Account.representative.reports
end
def status_text
@status_text ||= @status.text.unicode_normalize(:nfkc).downcase
end
end