twitter_ebooks 2.3.2 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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)