Noosfero/noosfero

View on GitHub
plugins/spaminator/lib/spaminator_plugin/spaminator.rb

Summary

Maintainability
A
1 hr
Test Coverage
# encoding: utf-8

class SpaminatorPlugin::Spaminator
  class << self
    def run(environment)
      instance = new(environment)
      instance.run
    end

    def benchmark(environment)
      puts Benchmark.measure { run(environment) }
    end

    def initialize_logger(environment)
      logdir = Rails.root.join("log", SpaminatorPlugin.name.underscore)
      FileUtils.mkdir_p(logdir) if !File.exist?(logdir)
      logpath = File.join(logdir, "#{environment.name.to_slug}_#{ENV['RAILS_ENV']}_#{Time.now.strftime('%F_%T')}.log")
      @logger = Logger.new(logpath)
    end

    def log(message)
      @logger << "[#{Time.now.strftime('%F %T %z')}] #{message}\n"
    end
  end

  def initialize(environment)
    @environment = environment
    @settings = Noosfero::Plugin::Settings.new(@environment, SpaminatorPlugin)
    @report = SpaminatorPlugin::Report.new({ environment: environment,
                                             total_people: Person.count,
                                             total_comments: Comment.count },
                                           { without_protection: true })
    self.class.initialize_logger(environment)
  end

  def run
    self.class.log("Starting Spaminator scan")
    start_time = Time.now

    process_all_comments
    process_all_people

    finish(start_time)
  end

  protected

    def finish(start_time)
      finish_report
      @settings.last_run = start_time
      @settings.save!
      self.class.log("Finishing Spaminator scan successfully")
    end

    # TODO considering run everything always
    def on_environment
      ["profiles.environment_id = ?", @environment.id]
    end

    def comments_to_process
      Comment.joins("JOIN articles ON (comments.source_id = articles.id AND comments.source_type = 'Article') JOIN profiles ON (profiles.id = articles.profile_id)").without_spam.where(on_environment)
    end

    def people_to_process
      Person.visible.non_abusers.where(on_environment)
    end

    def process_all_comments
      self.class.log("Starting to process all comments")
      comments = comments_to_process
      total = comments.count
      pbar = ProgressBar.create(title: "☢ Comments", total: total, format: "%t: |%B| %E") if Rails.env.development?
      comments.each do |comment|
        begin
          process_comment(comment)
        rescue
          register_fail(:comments, comment)
        end
        pbar.increment if Rails.env.development?
      end
      @report.processed_comments = total
      pbar.finish if Rails.env.development?
      self.class.log("All comments processed")
    end

    def process_all_people
      self.class.log("Starting to process all people")
      people = people_to_process
      total = people.count
      pbar = ProgressBar.create(title: "☢ People", total: total, format: "%t: |%B| %E") if Rails.env.development?
      people.find_each do |person|
        process_person_by_comments(person)
        process_person_by_no_network(person)
        pbar.increment if Rails.env.development?
      end
      @report.processed_people = total
      pbar.finish if Rails.env.development?
      self.class.log("All people processed")
    end

    def process_comment(comment)
      self.class.log("Processing Comment[#{comment.id.to_s}]")
      comment = Comment.find(comment.id)
      comment.check_for_spam
      @report.spams_by_content += 1 if comment.spam

      # TODO several comments with the same content:
      #   → disable author
      #   → mark all of them as spam

      # TODO check comments that contains URL's
    end

    def process_person_by_comments(person)
      # person is author of more than 2 comments marked as spam
      #   → mark as spammer
      #
      self.class.log("Processing Person[#{person.id.to_s}] by comments")
      begin
        number_of_spam_comments = Comment.spam.where(author_id: person.id).count
        if number_of_spam_comments > 2
          mark_as_spammer(person)
          @report.spammers_by_comments += 1
        end
      rescue
        register_fail(:people, person)
      end
    end

    def process_person_by_no_network(person)
      # person who signed up more than one month ago, have no friends and <= 1
      # communities
      #
      #   → disable the profile
      #   ? mark their comments as spam
      #
      self.class.log("Processing Person[#{person.id.to_s}] by network")
      if person.created_at < (Time.now - 1.month) &&
         person.friends.count == 0 &&
         person.communities.count <= 1
        begin
          disable_person(person)
          @report.spammers_by_no_network += 1
        rescue
          register_fail(:people, person)
        end
        Comment.where(author_id: person.id).find_each do |comment|
          begin
            comment.spam!
            @report.spams_by_no_network += 1
          rescue
            register_fail(:comments, comment)
          end
        end
      end
    end

    def disable_person(person)
      if person.disable
        Delayed::Job.enqueue(SpaminatorPlugin::Mailer::Job.new(person, :inactive_person_notification))
      end
    end

    def mark_as_spammer(person)
      AbuseComplaint.create!(reported: person).finish
    end

    def finish_report
      puts @report.details
      @report.save!
    end

    def register_fail(kind, failed)
      self.class.log("Failed #{kind.to_s.camelize}[#{failed.id.to_s}]")
      @report[:failed][kind.to_sym] << failed.id
    end
end