342 lines
		
	
	
	
		
			8.4 KiB
		
	
	
	
		
			Ruby
		
	
	
		
			Executable file
		
	
	
	
	
			
		
		
	
	
			342 lines
		
	
	
	
		
			8.4 KiB
		
	
	
	
		
			Ruby
		
	
	
		
			Executable file
		
	
	
	
	
#!/usr/bin/env ruby
 | 
						|
# encoding: utf-8
 | 
						|
 | 
						|
require 'twitter_ebooks'
 | 
						|
require 'ostruct'
 | 
						|
 | 
						|
module Ebooks::Util
 | 
						|
  def pretty_exception(e)
 | 
						|
 | 
						|
  end
 | 
						|
end
 | 
						|
 | 
						|
module Ebooks::CLI
 | 
						|
  APP_PATH = Dir.pwd # XXX do some recursive thing instead
 | 
						|
  HELP = OpenStruct.new
 | 
						|
 | 
						|
  HELP.default = <<STR
 | 
						|
Usage:
 | 
						|
     ebooks help <command>
 | 
						|
 | 
						|
     ebooks new <reponame>
 | 
						|
     ebooks s[tart]
 | 
						|
     ebooks c[onsole]
 | 
						|
     ebooks auth
 | 
						|
     ebooks consume <corpus_path> [corpus_path2] [...]
 | 
						|
     ebooks consume-all <corpus_path> [corpus_path2] [...]
 | 
						|
     ebooks gen <model_path> [input]
 | 
						|
     ebooks archive <username> [path]
 | 
						|
     ebooks tweet <model_path> <botname>
 | 
						|
STR
 | 
						|
 | 
						|
  def self.help(command=nil)
 | 
						|
    if command.nil?
 | 
						|
      log HELP.default
 | 
						|
    else
 | 
						|
      log HELP[command].gsub(/^ {4}/, '')
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  HELP.new = <<-STR
 | 
						|
    Usage: ebooks new <reponame>
 | 
						|
 | 
						|
    Creates a new skeleton repository defining a template bot in
 | 
						|
    the current working directory specified by <reponame>.
 | 
						|
  STR
 | 
						|
 | 
						|
  def self.new(reponame)
 | 
						|
    if reponame.nil?
 | 
						|
      help :new
 | 
						|
      exit 1
 | 
						|
    end
 | 
						|
 | 
						|
    path = "./#{reponame}"
 | 
						|
 | 
						|
    if File.exists?(path)
 | 
						|
      log "#{path} already exists. Please remove if you want to recreate."
 | 
						|
      exit 1
 | 
						|
    end
 | 
						|
 | 
						|
    FileUtils.cp_r(Ebooks::SKELETON_PATH, path)
 | 
						|
    FileUtils.mv(File.join(path, 'gitignore'), File.join(path, '.gitignore'))
 | 
						|
 | 
						|
    File.open(File.join(path, 'bots.rb'), 'w') do |f|
 | 
						|
      template = File.read(File.join(Ebooks::SKELETON_PATH, 'bots.rb'))
 | 
						|
      f.write(template.gsub("{{BOT_NAME}}", reponame))
 | 
						|
    end
 | 
						|
 | 
						|
    File.open(File.join(path, 'Gemfile'), 'w') do |f|
 | 
						|
      template = File.read(File.join(Ebooks::SKELETON_PATH, 'Gemfile'))
 | 
						|
      f.write(template.gsub("{{RUBY_VERSION}}", RUBY_VERSION))
 | 
						|
    end
 | 
						|
 | 
						|
    log "New twitter_ebooks app created at #{reponame}"
 | 
						|
  end
 | 
						|
 | 
						|
  HELP.consume = <<-STR
 | 
						|
    Usage: ebooks consume <corpus_path> [corpus_path2] [...]
 | 
						|
 | 
						|
    Processes some number of text files or json tweet corpuses
 | 
						|
    into usable models. These will be output at model/<name>.model
 | 
						|
  STR
 | 
						|
 | 
						|
  def self.consume(pathes)
 | 
						|
    if pathes.empty?
 | 
						|
      help :consume
 | 
						|
      exit 1
 | 
						|
    end
 | 
						|
 | 
						|
    pathes.each do |path|
 | 
						|
      filename = File.basename(path)
 | 
						|
      shortname = filename.split('.')[0..-2].join('.')
 | 
						|
 | 
						|
      outpath = File.join(APP_PATH, 'model', "#{shortname}.model")
 | 
						|
      Ebooks::Model.consume(path).save(outpath)
 | 
						|
      log "Corpus consumed to #{outpath}"
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  HELP.consume_all = <<-STR
 | 
						|
    Usage: ebooks consume-all <name> <corpus_path> [corpus_path2] [...]
 | 
						|
 | 
						|
    Processes some number of text files or json tweet corpuses
 | 
						|
    into one usable model. It will be output at model/<name>.model
 | 
						|
  STR
 | 
						|
 | 
						|
  def self.consume_all(name, paths)
 | 
						|
    if paths.empty?
 | 
						|
      help :consume_all
 | 
						|
      exit 1
 | 
						|
    end
 | 
						|
 | 
						|
    outpath = File.join(APP_PATH, 'model', "#{name}.model")
 | 
						|
    Ebooks::Model.consume_all(paths).save(outpath)
 | 
						|
    log "Corpuses consumed to #{outpath}"
 | 
						|
  end
 | 
						|
 | 
						|
  HELP.gen = <<-STR
 | 
						|
    Usage: ebooks gen <model_path> [input]
 | 
						|
 | 
						|
    Make a test tweet from the processed model at <model_path>.
 | 
						|
    Will respond to input if provided.
 | 
						|
  STR
 | 
						|
 | 
						|
  def self.gen(model_path, input)
 | 
						|
    if model_path.nil?
 | 
						|
      help :gen
 | 
						|
      exit 1
 | 
						|
    end
 | 
						|
 | 
						|
    model = Ebooks::Model.load(model_path)
 | 
						|
    if input && !input.empty?
 | 
						|
      puts "@cmd " + model.make_response(input, 135)
 | 
						|
    else
 | 
						|
      puts model.make_statement
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  HELP.archive = <<-STR
 | 
						|
    Usage: ebooks archive <username> [outpath]
 | 
						|
 | 
						|
    Downloads a json corpus of the <username>'s tweets.
 | 
						|
    Output defaults to corpus/<username>.json
 | 
						|
    Due to API limitations, this can only receive up to ~3000 tweets
 | 
						|
    into the past.
 | 
						|
  STR
 | 
						|
 | 
						|
  def self.archive(username, outpath=nil)
 | 
						|
    if username.nil?
 | 
						|
      help :archive
 | 
						|
      exit 1
 | 
						|
    end
 | 
						|
 | 
						|
    Ebooks::Archive.new(username, outpath).sync
 | 
						|
  end
 | 
						|
 | 
						|
  HELP.tweet = <<-STR
 | 
						|
    Usage: ebooks tweet <model_path> <botname>
 | 
						|
 | 
						|
    Sends a public tweet from the specified bot using text
 | 
						|
    from the processed model at <model_path>.
 | 
						|
  STR
 | 
						|
 | 
						|
  def self.tweet(modelpath, botname)
 | 
						|
    if modelpath.nil? || botname.nil?
 | 
						|
      help :tweet
 | 
						|
      exit 1
 | 
						|
    end
 | 
						|
 | 
						|
    load File.join(APP_PATH, 'bots.rb')
 | 
						|
    model = Ebooks::Model.load(modelpath)
 | 
						|
    statement = model.make_statement
 | 
						|
    bot = Ebooks::Bot.get(botname)
 | 
						|
    bot.configure
 | 
						|
    bot.tweet(statement)
 | 
						|
  end
 | 
						|
 | 
						|
  HELP.auth = <<-STR
 | 
						|
    Usage: ebooks auth
 | 
						|
 | 
						|
    Authenticates your Twitter app for any account. By default, will
 | 
						|
    use the consumer key and secret from the first defined bot. You
 | 
						|
    can specify another by setting the CONSUMER_KEY and CONSUMER_SECRET
 | 
						|
    environment variables.
 | 
						|
  STR
 | 
						|
 | 
						|
  def self.auth
 | 
						|
    consumer_key, consumer_secret = find_consumer
 | 
						|
    require 'oauth'
 | 
						|
 | 
						|
    consumer = OAuth::Consumer.new(
 | 
						|
      consumer_key,
 | 
						|
      consumer_secret,
 | 
						|
      site: 'https://twitter.com/',
 | 
						|
      scheme: :header
 | 
						|
    )
 | 
						|
 | 
						|
    request_token = consumer.get_request_token
 | 
						|
    auth_url = request_token.authorize_url()
 | 
						|
 | 
						|
    pin = nil
 | 
						|
    loop do
 | 
						|
      log auth_url
 | 
						|
 | 
						|
      log "Go to the above url and follow the prompts, then enter the PIN code here."
 | 
						|
      print "> "
 | 
						|
 | 
						|
      pin = STDIN.gets.chomp
 | 
						|
 | 
						|
      break unless pin.empty?
 | 
						|
    end
 | 
						|
 | 
						|
    access_token = request_token.get_access_token(oauth_verifier: pin)
 | 
						|
 | 
						|
    log "Account authorized successfully. Make sure to put these in your bots.rb!\n" +
 | 
						|
         "  access token: #{access_token.token}\n" +
 | 
						|
         "  access token secret: #{access_token.secret}"
 | 
						|
  end
 | 
						|
 | 
						|
  HELP.console = <<-STR
 | 
						|
    Usage: ebooks c[onsole]
 | 
						|
 | 
						|
    Starts an interactive ruby session with your bots loaded
 | 
						|
    and configured.
 | 
						|
  STR
 | 
						|
 | 
						|
  def self.console
 | 
						|
    load_bots
 | 
						|
    require 'pry'; Ebooks.module_exec { pry }
 | 
						|
  end
 | 
						|
 | 
						|
  HELP.start = <<-STR
 | 
						|
    Usage: ebooks s[tart] [botname]
 | 
						|
 | 
						|
    Starts running bots. If botname is provided, only runs that bot.
 | 
						|
  STR
 | 
						|
 | 
						|
  def self.start(botname=nil)
 | 
						|
    load_bots
 | 
						|
 | 
						|
    if botname.nil?
 | 
						|
      bots = Ebooks::Bot.all
 | 
						|
    else
 | 
						|
      bots = Ebooks::Bot.all.select { |bot| bot.username == botname }
 | 
						|
      if bots.empty?
 | 
						|
        log "Couldn't find a defined bot for @#{botname}!"
 | 
						|
        exit 1
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    threads = []
 | 
						|
    bots.each do |bot|
 | 
						|
      threads << Thread.new { bot.prepare }
 | 
						|
    end
 | 
						|
    threads.each(&:join)
 | 
						|
 | 
						|
    threads = []
 | 
						|
    bots.each do |bot|
 | 
						|
      threads << Thread.new do
 | 
						|
        loop do
 | 
						|
          begin
 | 
						|
            bot.start
 | 
						|
          rescue Exception => e
 | 
						|
            bot.log e.inspect
 | 
						|
            puts e.backtrace.map { |s| "\t"+s }.join("\n")
 | 
						|
          end
 | 
						|
          bot.log "Sleeping before reconnect"
 | 
						|
          sleep 60
 | 
						|
        end
 | 
						|
      end
 | 
						|
    end
 | 
						|
    threads.each(&:join)
 | 
						|
  end
 | 
						|
 | 
						|
  # Non-command methods
 | 
						|
 | 
						|
  def self.find_consumer
 | 
						|
    if ENV['CONSUMER_KEY'] && ENV['CONSUMER_SECRET']
 | 
						|
      log "Using consumer details from environment variables:\n" +
 | 
						|
          "  consumer key: #{ENV['CONSUMER_KEY']}\n" +
 | 
						|
          "  consumer secret: #{ENV['CONSUMER_SECRET']}"
 | 
						|
      return [ENV['CONSUMER_KEY'], ENV['CONSUMER_SECRET']]
 | 
						|
    end
 | 
						|
 | 
						|
    load_bots
 | 
						|
    consumer_key = nil
 | 
						|
    consumer_secret = nil
 | 
						|
    Ebooks::Bot.all.each do |bot|
 | 
						|
      if bot.consumer_key && bot.consumer_secret
 | 
						|
        consumer_key = bot.consumer_key
 | 
						|
        consumer_secret = bot.consumer_secret
 | 
						|
        log "Using consumer details from @#{bot.username}:\n" +
 | 
						|
            "  consumer key: #{bot.consumer_key}\n" +
 | 
						|
            "  consumer secret: #{bot.consumer_secret}\n"
 | 
						|
        return consumer_key, consumer_secret
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    if consumer_key.nil? || consumer_secret.nil?
 | 
						|
      log "Couldn't find any consumer details to auth an account with.\n" +
 | 
						|
          "Please either configure a bot with consumer_key and consumer_secret\n" +
 | 
						|
          "or provide the CONSUMER_KEY and CONSUMER_SECRET environment variables."
 | 
						|
      exit 1
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  def self.load_bots
 | 
						|
    load 'bots.rb'
 | 
						|
 | 
						|
    if Ebooks::Bot.all.empty?
 | 
						|
      puts "Couldn't find any bots! Please make sure bots.rb instantiates at least one bot."
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  def self.command(args)
 | 
						|
    if args.length == 0
 | 
						|
      help
 | 
						|
      exit 1
 | 
						|
    end
 | 
						|
 | 
						|
    case args[0]
 | 
						|
    when "new" then new(args[1])
 | 
						|
    when "consume" then consume(args[1..-1])
 | 
						|
    when "consume-all" then consume_all(args[1], args[2..-1])
 | 
						|
    when "gen" then gen(args[1], args[2..-1].join(' '))
 | 
						|
    when "archive" then archive(args[1], args[2])
 | 
						|
    when "tweet" then tweet(args[1], args[2])
 | 
						|
    when "jsonify" then jsonify(args[1..-1])
 | 
						|
    when "auth" then auth
 | 
						|
    when "console" then console
 | 
						|
    when "c" then console
 | 
						|
    when "start" then start(args[1])
 | 
						|
    when "s" then start(args[1])
 | 
						|
    when "help" then help(args[1])
 | 
						|
    else
 | 
						|
      log "No such command '#{args[0]}'"
 | 
						|
      help
 | 
						|
      exit 1
 | 
						|
    end
 | 
						|
  end
 | 
						|
end
 | 
						|
 | 
						|
Ebooks::CLI.command(ARGV)
 |