twitter_ebooks 2.3.2 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6c11c99337f28c765e0061e6e9a541ae3def2eb3
4
- data.tar.gz: aeb8bf213b41972ea1257e3b5f4bba14a2df88c3
3
+ metadata.gz: 5207fc0b43e38b2aeefa7f995be02cbbdf98f1d2
4
+ data.tar.gz: 79dd79c4cfeee86ff9bb20e790f19473156cfbba
5
5
  SHA512:
6
- metadata.gz: 07955ae0c6fb60549ad3fb48dc742ea6378e889c9ef9300bce7a78ef56ab11990c1a2d76f5046f62b1b75dca9dbb5165860d70d5db3dc6a7e5be28915a306f19
7
- data.tar.gz: 3d4a3459c7fe881c92773fe3d0b40dc32e440aaa208cf95f010a8d541fa826760a86370adcbbda1a4f93e448ace3d3089fd454eb808c37cdbbcd0bdbd870dc04
6
+ metadata.gz: da424019fae0c0b9383be7fcc1feb80a562f3612c6991f49eeda4dd3864c905ce6e3c0497b85ed001ec2eac7d81d2b92a0fd1271bad8f6c539ad017852e8e11f
7
+ data.tar.gz: e332b40106b7dd9df79704e80ee67fe4798458c8cb483f21e1302dd99f3cfa5a1f6dcf3327654e605c13f42884abc67f3eccd30f4475c7a62e2d7ea882bc894f
data/.gitignore CHANGED
@@ -1,3 +1,5 @@
1
1
  .*.swp
2
2
  Gemfile.lock
3
3
  pkg
4
+ .yardoc
5
+ doc
@@ -0,0 +1,7 @@
1
+ rvm:
2
+ - 2.1.4
3
+ script:
4
+ - rspec spec
5
+ notifications:
6
+ email:
7
+ - ebooks@mispy.me
data/README.md CHANGED
@@ -1,10 +1,26 @@
1
- # twitter\_ebooks 2.3.2
1
+ # twitter\_ebooks
2
2
 
3
- Rewrite of my twitter\_ebooks code. While the original was solely a tweeting Markov generator, this framework helps you build any kind of interactive twitterbot which responds to mentions/DMs. See [ebooks\_example](https://github.com/mispy/ebooks_example) for an example of a full bot.
3
+ [![Gem Version](https://badge.fury.io/rb/twitter_ebooks.svg)](http://badge.fury.io/rb/twitter_ebooks)
4
+ [![Build Status](https://travis-ci.org/mispy/twitter_ebooks.svg)](https://travis-ci.org/mispy/twitter_ebooks)
5
+ [![Dependency Status](https://gemnasium.com/mispy/twitter_ebooks.svg)](https://gemnasium.com/mispy/twitter_ebooks)
6
+
7
+ A framework for building interactive twitterbots which respond to mentions/DMs. See [ebooks_example](https://github.com/mispy/ebooks_example) for a fully-fledged bot definition.
8
+
9
+ ## New in 3.0
10
+
11
+ - Bots run in their own threads (no eventmachine), and startup is parallelized
12
+ - Bots start with `ebooks start`, and no longer die on unhandled exceptions
13
+ - `ebooks auth` command will create new access tokens, for running multiple bots
14
+ - `ebooks console` starts a ruby interpreter with bots loaded (see Ebooks::Bot.all)
15
+ - Replies are slightly rate-limited to prevent infinite bot convos
16
+ - Non-participating users in a mention chain will be dropped after a few tweets
17
+ - [API documentation](http://rdoc.info/github/mispy/twitter_ebooks)
18
+
19
+ Note that 3.0 is not backwards compatible with 2.x, so upgrade carefully!
4
20
 
5
21
  ## Installation
6
22
 
7
- Requires Ruby 1.9.3+ (2.1+ recommended)
23
+ Requires Ruby 2.0+
8
24
 
9
25
  ```bash
10
26
  gem install twitter_ebooks
@@ -16,48 +32,63 @@ Run `ebooks new <reponame>` to generate a new repository containing a sample bot
16
32
 
17
33
  ``` ruby
18
34
  # This is an example bot definition with event handlers commented out
19
- # You can define as many of these as you like; they will run simultaneously
35
+ # You can define and instantiate as many bots as you like
36
+
37
+ class MyBot < Ebooks::Bot
38
+ # Configuration here applies to all MyBots
39
+ def configure
40
+ # Consumer details come from registering an app at https://dev.twitter.com/
41
+ # Once you have consumer details, use "ebooks auth" for new access tokens
42
+ self.consumer_key = '' # Your app consumer key
43
+ self.consumer_secret = '' # Your app consumer secret
20
44
 
21
- Ebooks::Bot.new("abby_ebooks") do |bot|
22
- # Consumer details come from registering an app at https://dev.twitter.com/
23
- # OAuth details can be fetched with https://github.com/marcel/twurl
24
- bot.consumer_key = "" # Your app consumer key
25
- bot.consumer_secret = "" # Your app consumer secret
26
- bot.oauth_token = "" # Token connecting the app to this account
27
- bot.oauth_token_secret = "" # Secret connecting the app to this account
45
+ # Users to block instead of interacting with
46
+ self.blacklist = ['tnietzschequote']
28
47
 
29
- bot.on_message do |dm|
48
+ # Range in seconds to randomize delay when bot.delay is called
49
+ self.delay_range = 1..6
50
+ end
51
+
52
+ def on_startup
53
+ scheduler.every '24h' do
54
+ # Tweet something every 24 hours
55
+ # See https://github.com/jmettraux/rufus-scheduler
56
+ # bot.tweet("hi")
57
+ # bot.pictweet("hi", "cuteselfie.jpg")
58
+ end
59
+ end
60
+
61
+ def on_message(dm)
30
62
  # Reply to a DM
31
63
  # bot.reply(dm, "secret secrets")
32
64
  end
33
65
 
34
- bot.on_follow do |user|
66
+ def on_follow(user)
35
67
  # Follow a user back
36
68
  # bot.follow(user[:screen_name])
37
69
  end
38
70
 
39
- bot.on_mention do |tweet, meta|
71
+ def on_mention(tweet)
40
72
  # Reply to a mention
41
- # bot.reply(tweet, meta[:reply_prefix] + "oh hullo")
73
+ # bot.reply(tweet, meta(tweet)[:reply_prefix] + "oh hullo")
42
74
  end
43
75
 
44
- bot.on_timeline do |tweet, meta|
76
+ def on_timeline(tweet)
45
77
  # Reply to a tweet in the bot's timeline
46
- # bot.reply(tweet, meta[:reply_prefix] + "nice tweet")
78
+ # bot.reply(tweet, meta(tweet)[:reply_prefix] + "nice tweet")
47
79
  end
80
+ end
48
81
 
49
- bot.scheduler.every '24h' do
50
- # Tweet something every 24 hours
51
- # See https://github.com/jmettraux/rufus-scheduler
52
- # bot.tweet("hi")
53
- # bot.pictweet("hi", "cuteselfie.jpg", ":possibly_sensitive => true")
54
- end
82
+ # Make a MyBot and attach it to an account
83
+ MyBot.new("{{BOT_NAME}}") do |bot|
84
+ bot.access_token = "" # Token connecting the app to this account
85
+ bot.access_token_secret = "" # Secret connecting the app to this account
55
86
  end
56
87
  ```
57
88
 
58
- Bots defined like this can be spawned by executing `run.rb` in the same directory, and will operate together in a single eventmachine loop. The easiest way to run bots in a semi-permanent fashion is with [Heroku](https://www.heroku.com); just make an app, push the bot repository to it, enable a worker process in the web interface and it ought to chug along merrily forever.
89
+ 'ebooks start' will run all defined bots in their own threads. The easiest way to run bots in a semi-permanent fashion is with [Heroku](https://www.heroku.com); just make an app, push the bot repository to it, enable a worker process in the web interface and it ought to chug along merrily forever.
59
90
 
60
- The underlying [tweetstream](https://github.com/tweetstream/tweetstream) and [twitter gem](https://github.com/sferik/twitter) client objects can be accessed at `bot.stream` and `bot.twitter` respectively.
91
+ The underlying streaming and REST clients from the [twitter gem](https://github.com/sferik/twitter) can be accessed at `bot.stream` and `bot.twitter` respectively.
61
92
 
62
93
  ## Archiving accounts
63
94
 
@@ -92,7 +123,6 @@ Text files use newlines and full stops to seperate statements.
92
123
  Once you have a model, the primary use is to produce statements and related responses to input, using a pseudo-Markov generator:
93
124
 
94
125
  ``` ruby
95
- > require 'twitter_ebooks'
96
126
  > model = Ebooks::Model.load("model/0xabad1dea.model")
97
127
  > model.make_statement(140)
98
128
  => "My Terrible Netbook may be the kind of person who buys Starbucks, but this Rackspace vuln is pretty straight up a backdoor"
@@ -103,14 +133,14 @@ Once you have a model, the primary use is to produce statements and related resp
103
133
  The secondary function is the "interesting keywords" list. For example, I use this to determine whether a bot wants to fav/retweet/reply to something in its timeline:
104
134
 
105
135
  ``` ruby
106
- top100 = model.keywords.top(100)
136
+ top100 = model.keywords.take(100)
107
137
  tokens = Ebooks::NLP.tokenize(tweet[:text])
108
138
 
109
139
  if tokens.find { |t| top100.include?(t) }
110
- bot.twitter.favorite(tweet[:id])
140
+ bot.favorite(tweet[:id])
111
141
  end
112
142
  ```
113
143
 
114
- ## Other notes
144
+ ## Bot niceness
115
145
 
116
- If you're using Heroku, which has no persistent filesystem, automating the process of archiving, consuming and updating can be tricky. My current solution is just a daily cron job which commits and pushes for me, which is pretty hacky.
146
+ twitter_ebooks will drop bystanders from mentions for you and avoid infinite bot conversations, but it won't prevent you from doing a lot of other spammy things. Make sure your bot is a good and polite citizen!
data/bin/ebooks CHANGED
@@ -2,54 +2,85 @@
2
2
  # encoding: utf-8
3
3
 
4
4
  require 'twitter_ebooks'
5
- require 'csv'
5
+ require 'ostruct'
6
6
 
7
- $debug = true
7
+ module Ebooks::Util
8
+ def pretty_exception(e)
8
9
 
9
- module Ebooks
10
+ end
11
+ end
12
+
13
+ module Ebooks::CLI
10
14
  APP_PATH = Dir.pwd # XXX do some recursive thing instead
15
+ HELP = OpenStruct.new
11
16
 
12
- def self.new(reponame)
13
- usage = <<STR
14
- Usage: ebooks new <reponame>
17
+ HELP.default = <<STR
18
+ Usage:
19
+ ebooks help <command>
15
20
 
16
- Creates a new skeleton repository defining a template bot in
17
- the current working directory specified by <reponame>.
21
+ ebooks new <reponame>
22
+ ebooks auth
23
+ ebooks consume <corpus_path> [corpus_path2] [...]
24
+ ebooks consume-all <corpus_path> [corpus_path2] [...]
25
+ ebooks gen <model_path> [input]
26
+ ebooks archive <username> [path]
27
+ ebooks tweet <model_path> <botname>
18
28
  STR
19
29
 
30
+ def self.help(command=nil)
31
+ if command.nil?
32
+ log HELP.default
33
+ else
34
+ log HELP[command].gsub(/^ {4}/, '')
35
+ end
36
+ end
37
+
38
+ HELP.new = <<-STR
39
+ Usage: ebooks new <reponame>
40
+
41
+ Creates a new skeleton repository defining a template bot in
42
+ the current working directory specified by <reponame>.
43
+ STR
44
+
45
+ def self.new(reponame)
20
46
  if reponame.nil?
21
- log usage
22
- exit
47
+ help :new
48
+ exit 1
23
49
  end
24
50
 
25
51
  path = "./#{reponame}"
26
52
 
27
53
  if File.exists?(path)
28
54
  log "#{path} already exists. Please remove if you want to recreate."
29
- exit
55
+ exit 1
30
56
  end
31
57
 
32
- FileUtils.cp_r(SKELETON_PATH, path)
58
+ FileUtils.cp_r(Ebooks::SKELETON_PATH, path)
33
59
 
34
60
  File.open(File.join(path, 'bots.rb'), 'w') do |f|
35
- template = File.read(File.join(SKELETON_PATH, 'bots.rb'))
61
+ template = File.read(File.join(Ebooks::SKELETON_PATH, 'bots.rb'))
36
62
  f.write(template.gsub("{{BOT_NAME}}", reponame))
37
63
  end
38
64
 
65
+ File.open(File.join(path, 'Gemfile'), 'w') do |f|
66
+ template = File.read(File.join(Ebooks::SKELETON_PATH, 'Gemfile'))
67
+ f.write(template.gsub("{{RUBY_VERSION}}", RUBY_VERSION))
68
+ end
69
+
39
70
  log "New twitter_ebooks app created at #{reponame}"
40
71
  end
41
72
 
42
- def self.consume(pathes)
43
- usage = <<STR
44
- Usage: ebooks consume <corpus_path> [corpus_path2] [...]
73
+ HELP.consume = <<-STR
74
+ Usage: ebooks consume <corpus_path> [corpus_path2] [...]
45
75
 
46
- Processes some number of text files or json tweet corpuses
47
- into usable models. These will be output at model/<name>.model
48
- STR
76
+ Processes some number of text files or json tweet corpuses
77
+ into usable models. These will be output at model/<name>.model
78
+ STR
49
79
 
80
+ def self.consume(pathes)
50
81
  if pathes.empty?
51
- log usage
52
- exit
82
+ help :consume
83
+ exit 1
53
84
  end
54
85
 
55
86
  pathes.each do |path|
@@ -57,24 +88,43 @@ STR
57
88
  shortname = filename.split('.')[0..-2].join('.')
58
89
 
59
90
  outpath = File.join(APP_PATH, 'model', "#{shortname}.model")
60
- Model.consume(path).save(outpath)
91
+ Ebooks::Model.consume(path).save(outpath)
61
92
  log "Corpus consumed to #{outpath}"
62
93
  end
63
94
  end
64
95
 
65
- def self.gen(model_path, input)
66
- usage = <<STR
67
- Usage: ebooks gen <model_path> [input]
96
+ HELP.consume_all = <<-STR
97
+ Usage: ebooks consume-all <name> <corpus_path> [corpus_path2] [...]
68
98
 
69
- Make a test tweet from the processed model at <model_path>.
70
- Will respond to input if provided.
71
- STR
99
+ Processes some number of text files or json tweet corpuses
100
+ into one usable model. It will be output at model/<name>.model
101
+ STR
102
+
103
+ def self.consume_all(name, paths)
104
+ if paths.empty?
105
+ help :consume_all
106
+ exit 1
107
+ end
108
+
109
+ outpath = File.join(APP_PATH, 'model', "#{name}.model")
110
+ Ebooks::Model.consume_all(paths).save(outpath)
111
+ log "Corpuses consumed to #{outpath}"
112
+ end
113
+
114
+ HELP.gen = <<-STR
115
+ Usage: ebooks gen <model_path> [input]
116
+
117
+ Make a test tweet from the processed model at <model_path>.
118
+ Will respond to input if provided.
119
+ STR
120
+
121
+ def self.gen(model_path, input)
72
122
  if model_path.nil?
73
- log usage
74
- exit
123
+ help :gen
124
+ exit 1
75
125
  end
76
126
 
77
- model = Model.load(model_path)
127
+ model = Ebooks::Model.load(model_path)
78
128
  if input && !input.empty?
79
129
  puts "@cmd " + model.make_response(input, 135)
80
130
  else
@@ -82,136 +132,208 @@ STR
82
132
  end
83
133
  end
84
134
 
85
- def self.score(model_path, input)
86
- usage = <<STR
87
- Usage: ebooks score <model_path> <input>
88
-
89
- Scores "interest" in some text input according to how
90
- well unique keywords match the model.
91
- STR
92
- if model_path.nil? || input.nil?
93
- log usage
94
- exit
95
- end
135
+ HELP.archive = <<-STR
136
+ Usage: ebooks archive <username> [outpath]
96
137
 
97
- model = Model.load(model_path)
98
- model.score_interest(input)
99
- end
138
+ Downloads a json corpus of the <username>'s tweets.
139
+ Output defaults to corpus/<username>.json
140
+ Due to API limitations, this can only receive up to ~3000 tweets
141
+ into the past.
142
+ STR
100
143
 
101
- def self.archive(username, outpath)
102
- usage = <<STR
103
- Usage: ebooks archive <username> <outpath>
104
-
105
- Downloads a json corpus of the <username>'s tweets to <outpath>.
106
- Due to API limitations, this can only receive up to ~3000 tweets
107
- into the past.
108
- STR
109
-
110
- if username.nil? || outpath.nil?
111
- log usage
112
- exit
144
+ def self.archive(username, outpath=nil)
145
+ if username.nil?
146
+ help :archive
147
+ exit 1
113
148
  end
114
149
 
115
- Archive.new(username, outpath).sync
150
+ Ebooks::Archive.new(username, outpath).sync
116
151
  end
117
152
 
118
- def self.tweet(modelpath, botname)
119
- usage = <<STR
120
- Usage: ebooks tweet <model_path> <botname>
153
+ HELP.tweet = <<-STR
154
+ Usage: ebooks tweet <model_path> <botname>
121
155
 
122
- Sends a public tweet from the specified bot using text
123
- from the processed model at <model_path>.
124
- STR
156
+ Sends a public tweet from the specified bot using text
157
+ from the processed model at <model_path>.
158
+ STR
125
159
 
160
+ def self.tweet(modelpath, botname)
126
161
  if modelpath.nil? || botname.nil?
127
- log usage
128
- exit
162
+ help :tweet
163
+ exit 1
129
164
  end
130
165
 
131
166
  load File.join(APP_PATH, 'bots.rb')
132
- model = Model.load(modelpath)
167
+ model = Ebooks::Model.load(modelpath)
133
168
  statement = model.make_statement
134
- log "@#{botname}: #{statement}"
135
- bot = Bot.get(botname)
169
+ bot = Ebooks::Bot.get(botname)
136
170
  bot.configure
137
171
  bot.tweet(statement)
138
172
  end
139
173
 
140
- def self.jsonify(paths)
141
- usage = <<STR
142
- Usage: ebooks jsonify <old_corpus_path> [old_corpus_path2] [...]
174
+ HELP.auth = <<-STR
175
+ Usage: ebooks auth
143
176
 
144
- Takes an old-style corpus of plain tweet text and converts it to json.
145
- STR
177
+ Authenticates your Twitter app for any account. By default, will
178
+ use the consumer key and secret from the first defined bot. You
179
+ can specify another by setting the CONSUMER_KEY and CONSUMER_SECRET
180
+ environment variables.
181
+ STR
146
182
 
147
- if paths.empty?
148
- log usage
149
- exit
183
+ def self.auth
184
+ consumer_key, consumer_secret = find_consumer
185
+ require 'oauth'
186
+
187
+ consumer = OAuth::Consumer.new(
188
+ consumer_key,
189
+ consumer_secret,
190
+ site: 'https://twitter.com/',
191
+ scheme: :header
192
+ )
193
+
194
+ request_token = consumer.get_request_token
195
+ auth_url = request_token.authorize_url()
196
+
197
+ pin = nil
198
+ loop do
199
+ log auth_url
200
+
201
+ log "Go to the above url and follow the prompts, then enter the PIN code here."
202
+ print "> "
203
+
204
+ pin = STDIN.gets.chomp
205
+
206
+ break unless pin.empty?
150
207
  end
151
208
 
152
- paths.each do |path|
153
- name = File.basename(path).split('.')[0]
154
- new_path = name + ".json"
209
+ access_token = request_token.get_access_token(oauth_verifier: pin)
155
210
 
156
- tweets = []
157
- id = nil
158
- if path.split('.')[-1] == "csv" #from twitter archive
159
- csv_archive = CSV.read(path, :headers=>:first_row)
160
- tweets = csv_archive.map do |tweet|
161
- { text: tweet['text'], id: tweet['tweet_id'] }
162
- end
163
- else
164
- File.read(path).split("\n").each do |l|
165
- if l.start_with?('# ')
166
- id = l.split('# ')[-1]
167
- else
168
- tweet = { text: l }
169
- if id
170
- tweet[:id] = id
171
- id = nil
172
- end
173
- tweets << tweet
211
+ log "Account authorized successfully. Make sure to put these in your bots.rb!\n" +
212
+ " access token: #{access_token.token}\n" +
213
+ " access token secret: #{access_token.secret}"
214
+ end
215
+
216
+ HELP.console = <<-STR
217
+ Usage: ebooks c[onsole]
218
+
219
+ Starts an interactive ruby session with your bots loaded
220
+ and configured.
221
+ STR
222
+
223
+ def self.console
224
+ load_bots
225
+ require 'pry'; Ebooks.module_exec { pry }
226
+ end
227
+
228
+ HELP.start = <<-STR
229
+ Usage: ebooks s[tart] [botname]
230
+
231
+ Starts running bots. If botname is provided, only runs that bot.
232
+ STR
233
+
234
+ def self.start(botname=nil)
235
+ load_bots
236
+
237
+ if botname.nil?
238
+ bots = Ebooks::Bot.all
239
+ else
240
+ bots = Ebooks::Bot.all.select { |bot| bot.username == botname }
241
+ if bots.empty?
242
+ log "Couldn't find a defined bot for @#{botname}!"
243
+ exit 1
244
+ end
245
+ end
246
+
247
+ threads = []
248
+ bots.each do |bot|
249
+ threads << Thread.new { bot.prepare }
250
+ end
251
+ threads.each(&:join)
252
+
253
+ threads = []
254
+ bots.each do |bot|
255
+ threads << Thread.new do
256
+ loop do
257
+ begin
258
+ bot.start
259
+ rescue Exception => e
260
+ bot.log e.inspect
261
+ puts e.backtrace.map { |s| "\t"+s }.join("\n")
174
262
  end
263
+ bot.log "Sleeping before reconnect"
264
+ sleep 5
175
265
  end
176
266
  end
267
+ end
268
+ threads.each(&:join)
269
+ end
270
+
271
+ # Non-command methods
177
272
 
178
- File.open(new_path, 'w') do |f|
179
- log "Writing #{tweets.length} tweets to #{new_path}"
180
- f.write(JSON.pretty_generate(tweets))
273
+ def self.find_consumer
274
+ if ENV['CONSUMER_KEY'] && ENV['CONSUMER_SECRET']
275
+ log "Using consumer details from environment variables:\n" +
276
+ " consumer key: #{ENV['CONSUMER_KEY']}\n" +
277
+ " consumer secret: #{ENV['CONSUMER_SECRET']}"
278
+ return [ENV['CONSUMER_KEY'], ENV['CONSUMER_SECRET']]
279
+ end
280
+
281
+ load_bots
282
+ consumer_key = nil
283
+ consumer_secret = nil
284
+ Ebooks::Bot.all.each do |bot|
285
+ if bot.consumer_key && bot.consumer_secret
286
+ consumer_key = bot.consumer_key
287
+ consumer_secret = bot.consumer_secret
288
+ log "Using consumer details from @#{bot.username}:\n" +
289
+ " consumer key: #{bot.consumer_key}\n" +
290
+ " consumer secret: #{bot.consumer_secret}\n"
291
+ return consumer_key, consumer_secret
181
292
  end
182
293
  end
294
+
295
+ if consumer_key.nil? || consumer_secret.nil?
296
+ log "Couldn't find any consumer details to auth an account with.\n" +
297
+ "Please either configure a bot with consumer_key and consumer_secret\n" +
298
+ "or provide the CONSUMER_KEY and CONSUMER_SECRET environment variables."
299
+ exit 1
300
+ end
183
301
  end
184
302
 
185
- def self.command(args)
186
- usage = <<STR
187
- Usage:
188
- ebooks new <reponame>
189
- ebooks consume <corpus_path> [corpus_path2] [...]
190
- ebooks gen <model_path> [input]
191
- ebooks score <model_path> <input>
192
- ebooks archive <@user> <outpath>
193
- ebooks tweet <model_path> <botname>
194
- ebooks jsonify <old_corpus_path> [old_corpus_path2] [...]
195
- STR
303
+ def self.load_bots
304
+ load 'bots.rb'
196
305
 
306
+ if Ebooks::Bot.all.empty?
307
+ puts "Couldn't find any bots! Please make sure bots.rb instantiates at least one bot."
308
+ end
309
+ end
310
+
311
+ def self.command(args)
197
312
  if args.length == 0
198
- log usage
199
- exit
313
+ help
314
+ exit 1
200
315
  end
201
316
 
202
317
  case args[0]
203
318
  when "new" then new(args[1])
204
319
  when "consume" then consume(args[1..-1])
320
+ when "consume-all" then consume_all(args[1], args[2..-1])
205
321
  when "gen" then gen(args[1], args[2..-1].join(' '))
206
- when "score" then score(args[1], args[2..-1].join(' '))
207
322
  when "archive" then archive(args[1], args[2])
208
323
  when "tweet" then tweet(args[1], args[2])
209
324
  when "jsonify" then jsonify(args[1..-1])
325
+ when "auth" then auth
326
+ when "console" then console
327
+ when "c" then console
328
+ when "start" then start(args[1])
329
+ when "s" then start(args[1])
330
+ when "help" then help(args[1])
210
331
  else
211
- log usage
332
+ log "No such command '#{args[0]}'"
333
+ help
212
334
  exit 1
213
335
  end
214
336
  end
215
337
  end
216
338
 
217
- Ebooks.command(ARGV)
339
+ Ebooks::CLI.command(ARGV)