450 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Ruby
		
	
	
		
			Executable file
		
	
	
	
	
			
		
		
	
	
			450 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Ruby
		
	
	
		
			Executable file
		
	
	
	
	
| #!/usr/bin/env ruby
 | |
| # encoding: utf-8
 | |
| 
 | |
| require 'twitter_ebooks'
 | |
| require 'ostruct'
 | |
| require 'fileutils'
 | |
| 
 | |
| 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 <model_name> <corpus_path> [corpus_path2] [...]
 | |
|      ebooks append <model_name> <corpus_path>
 | |
|      ebooks gen <model_path> [input]
 | |
|      ebooks archive <username> [path]
 | |
|      ebooks sync <botname> [username]
 | |
|      ebooks tweet <model_path> <botname>
 | |
|      ebooks version
 | |
| 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/<corpus_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('.')
 | |
| 
 | |
|       FileUtils.mkdir_p(File.join(APP_PATH, 'model'))
 | |
|       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 <model_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/<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.append = <<-STR
 | |
|     Usage: ebooks append <model_name> <corpus_path>
 | |
| 
 | |
|     Process then append the provided corpus to the model
 | |
|     instead of overwriting.
 | |
|   STR
 | |
| 
 | |
|   def self.append(name, path)
 | |
|     if !name || !path
 | |
|       help :append
 | |
|       exit 1
 | |
|     end
 | |
| 
 | |
|     Ebooks::Model.consume(path).append(File.join(APP_PATH,'model',"#{name}.model"))
 | |
|     log "Corpus appended to #{name}.model"
 | |
|   end
 | |
| 
 | |
| 
 | |
|   HELP.jsonify = <<-STR
 | |
|     Usage: ebooks jsonify <tweets.csv> [tweets.csv2] [...]
 | |
| 
 | |
|     Takes a csv twitter archive and converts it to json.
 | |
|   STR
 | |
| 
 | |
|   def self.jsonify(paths)
 | |
|     if paths.empty?
 | |
|       log usage
 | |
|       exit
 | |
|     end
 | |
| 
 | |
|     paths.each do |path|
 | |
|       name = File.basename(path).split('.')[0]
 | |
|       new_path = name + ".json"
 | |
| 
 | |
|       tweets = []
 | |
|       id = nil
 | |
|       if path.split('.')[-1] == "csv" #from twitter archive
 | |
|         csv_archive = CSV.read(path, :headers=>:first_row)
 | |
|         tweets = csv_archive.map do |tweet|
 | |
|           { text: tweet['text'], id: tweet['tweet_id'] }
 | |
|         end
 | |
|       else
 | |
|         File.read(path).split("\n").each do |l|
 | |
|           if l.start_with?('# ')
 | |
|             id = l.split('# ')[-1]
 | |
|           else
 | |
|             tweet = { text: l }
 | |
|             if id
 | |
|               tweet[:id] = id
 | |
|               id = nil
 | |
|             end
 | |
|             tweets << tweet
 | |
|           end
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       File.open(new_path, 'w') do |f|
 | |
|         log "Writing #{tweets.length} tweets to #{new_path}"
 | |
|         f.write(JSON.pretty_generate(tweets))
 | |
|       end
 | |
|     end
 | |
|   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.
 | |
| 
 | |
|     The first time you run archive, you will need to enter the auth
 | |
|     details of some account to use for accessing the API. This info
 | |
|     will then be stored in ~/.ebooksrc for later use, and can be
 | |
|     modified there if needed.
 | |
|   STR
 | |
| 
 | |
|   def self.archive(username, outpath=nil)
 | |
|     if username.nil?
 | |
|       help :archive
 | |
|       exit 1
 | |
|     end
 | |
| 
 | |
|     Ebooks::Archive.new(username, outpath).sync
 | |
|   end
 | |
| 
 | |
|     HELP.sync = <<-STR
 | |
|     Usage: ebooks sync <botname> <username>
 | |
| 
 | |
|     Copies and flips <username>'s avatar and cover photo, uploading them to <botname>'s profile.
 | |
| 
 | |
|     Stores saved avatar's and covers in image/.
 | |
| 
 | |
|   STR
 | |
| 
 | |
|   def self.sync(botname, username)
 | |
|     if botname.nil?
 | |
|       help :sync
 | |
|       exit 1
 | |
|     end
 | |
| 
 | |
|     load File.join(APP_PATH, 'bots.rb')
 | |
|     Ebooks::Sync::run(botname, username)
 | |
|   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" +
 | |
|          "  bot.access_token = \"#{access_token.token}\"\n" +
 | |
|          "  bot.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.version = <<-STR
 | |
|     Usage: ebooks version
 | |
| 
 | |
|     Shows you twitter_ebooks' version number.
 | |
|   STR
 | |
| 
 | |
|   def self.version
 | |
|     require File.expand_path('../../lib/twitter_ebooks/version', __FILE__)
 | |
|     log Ebooks::VERSION
 | |
|   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 "append" then append(args[1],args[2])
 | |
|     when "gen" then gen(args[1], args[2..-1].join(' '))
 | |
|     when "archive" then archive(args[1], args[2])
 | |
|     when "sync" then sync(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])
 | |
|     when "version" then version
 | |
|     else
 | |
|       log "No such command '#{args[0]}'"
 | |
|       help
 | |
|       exit 1
 | |
|     end
 | |
|   end
 | |
| end
 | |
| 
 | |
| Ebooks::CLI.command(ARGV)
 | 
