twitter-ebooks/bin/ebooks

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)