<commit_before>#!/usr/bin/env ruby -w
# to run: dissident start

require 'twitter'
require 'socket'


# here are the heckles for a user
# designed so that they can be isolated/persisted if need be
class Heckles
  
  def init(username)
    @phrases = Array.new
    username = username.downcase
    filename = "data/#{username}.txt".downcase
    if not File.file?(filename) 
      puts "No data file #{filename}"
      return false
    end
    
    puts "Reading #{filename}"
    File.open(filename).readlines.each do | line |
      line.strip!
      if not line.empty? and not line.start_with?("\#")
        puts "#{line.length}: #{line}"
        if line.length < 140
          @phrases << line
        else
          puts "**LINE too long**"
        end
      end     
    end
    puts "Found #{@phrases.length} entries"
    return true
  end
  
  def heckle
    return @phrases.sample
  end
  
  def empty?
    return @phrases.length == 0
  end
end

# This is the class which does all the work
class Dissident

  # startup: inits the clients. It does not attempt to talk to Twitter though,
  # so invalid credentials are not picked up
  def initialize
    config = eval(File.open('conf/secrets.rb') {|f| f.read })
    @rest = Twitter::REST::Client.new(config)
    @streaming = Twitter::Streaming::Client.new(config)
    @myname = "dissidentbot"
  end

  # Generate a reply for the given user, if they are targeted and it is not a reply
  # the latter keeps the noise down, and avoids loops.
  def reply(tweet)
    puts "incoming tweet: #{tweet.user.screen_name}: #{tweet.full_text} in reply to \"#{tweet.in_reply_to_user_id}\" "
    
    return unless tweet.in_reply_to_status_id.is_a?(Twitter::NullObject)
    username = tweet.user.screen_name
    return if username.eql?@myname
    heckles = Heckles.new()
    heckles.init(username)
    return if heckles.empty?
    status = "@#{username} #{heckles.heckle}"
    if status.length > 140
      puts "Reply too long at #{status.length}: #{status}"
    else
      puts "tweeting #{status}"
      @rest.update(status, in_reply_to_status_id: tweet.id)      
    end
  end

  # get the shortname of this host for reporting
  def shortname
    fqdn = Socket.gethostname
    elements = fqdn.split(".")
    return elements[0]
  end
  
  # the list of targets
  def targets
    return Dir["data/*.txt"]
  end
  
  # get a count of targets
  def target_count
    return targets().length
  end
    
  # Say anything on twitter
  def say(message)
    @rest.update(message)
  end
  
  # Build the startup message
  def startup_message()
    t = Time.now.utc
    return "Dissenting from #{target_count} accounts on host #{shortname} at #{t.getlocal}"
  end
      
  # process a tweet *or other event*. 
  def process(event) 
    case event
    when Twitter::Tweet
      reply(event)
    when Twitter::DirectMessage
      puts "Direct message from #{event.user.screen_name}: #{event.full_text}"
    when Twitter::Streaming::StallWarning
      warn "Falling behind!"
    end
  end
<commit_msg>direct message status rsponse, though the "targets" response is flawed: overloads with too many dests. Better to list 1/response, maybe even count of each target's heckles<commit_after>#!/usr/bin/env ruby -w
# to run: dissident start

require 'twitter'
require 'socket'


# here are the heckles for a user
# designed so that they can be isolated/persisted if need be
class Heckles
  
  def init(username)
    @phrases = Array.new
    filename = "data/#{username}.txt".downcase
    if not File.file?(filename) 
      puts "No data file #{filename}"
      return false
    end
    
    puts "Reading #{filename}"
    File.open(filename).readlines.each do | line |
      line.strip!
      if not line.empty? and not line.start_with?("\#")
        puts "#{line.length}: #{line}"
        if line.length < 140
          @phrases << line
        else
          puts "**LINE too long**"
        end
      end     
    end
    puts "Found #{@phrases.length} entries"
    return true
  end
  
  def heckle
    return @phrases.sample
  end
  
  def empty?
    return @phrases.length == 0
  end
end

# This is the class which does all the work
class Dissident

  # startup: inits the clients. It does not attempt to talk to Twitter though,
  # so invalid credentials are not picked up
  def initialize
    config = eval(File.open('conf/secrets.rb') {|f| f.read })
    @rest = Twitter::REST::Client.new(config)
    @streaming = Twitter::Streaming::Client.new(config)
    @myname = "dissidentbot"
    @started = Time.now.utc
    @sent_count = 0
    @hostname = shortname()
  end

  # Generate a reply for the given user, if they are targeted and it is not a reply
  # the latter keeps the noise down, and avoids loops.
  def reply(tweet)
    puts "incoming tweet: #{tweet.user.screen_name}: #{tweet.text} in reply to \"#{tweet.in_reply_to_user_id}\" "
    
    return unless tweet.in_reply_to_status_id.is_a?(Twitter::NullObject)
    username = tweet.user.screen_name.downcase
    return if username.eql?@myname
    heckles = Heckles.new()
    heckles.init(username)
    return if heckles.empty?
    status = "@#{username} #{heckles.heckle}"
    if status.length > 140
      puts "Reply too long at #{status.length}: #{status}"
    else
      puts "tweeting #{status}"
      @started = @started + 1
      @rest.update(status, in_reply_to_status_id: tweet.id)      
    end
  end
  
  def build_direct_message(command)
    command.downcase.strip!
    s = "#{@hostname}: "
    case command
    when "status"
      s = s + "started #{@started}; targets #{target_count}; sent: #{@sent_count}"
    when "targets"
      s = s + targets.join(", ")
    else
      s = s + "usage: status | targets"
    end
    return s
  end
  
  # incoming is 
  def on_direct_message(event)
    user = event.sender
    username = user.screen_name.downcase
    return if username.eql?@myname
    puts "Direct message from #{user.screen_name}: #{event.text}"
    response = build_direct_message(event.text)
    @rest.create_direct_message(user, response)
  end
  

  # get the shortname of this host for reporting
  def shortname
    fqdn = Socket.gethostname
    elements = fqdn.split(".")
    return elements[0]
  end
  
  # the list of targets
  def targets
    return Dir["data/*.txt"]
  end
  
  # get a count of targets
  def target_count
    return targets().length
  end
    
  # Say anything on twitter
  def say(message)
    @rest.update(message)
  end
  
  # Build the startup message
  def startup_message()
    t = Time.now.utc
    return "Dissenting from #{target_count} accounts on host #{@hostname} at #{t.getlocal}"
  end
      
  # process a tweet *or other event*. 
  def process(event) 
    case event
    when Twitter::Tweet
      reply(event)
    when Twitter::DirectMessage
      on_direct_message(event)
    when Twitter::Streaming::StallWarning
      warn "Falling behind!"
    end
  end

{'fraction_non_alphanumeric': 0.10317345312273583, 'fraction_numerical': 0.0027532241704100856, 'mean_word_length': 2.763358778625954, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2674704', 'n_tokens_mistral': 2250, 'n_tokens_neox': 2211, 'n_words': 691}