foxdear_ebooks 3.1.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f7528806bec7562081adb681ae9d8c392d22a783
4
+ data.tar.gz: 04b06f7308d8e02cfedb756ec03bd029104b47d5
5
+ SHA512:
6
+ metadata.gz: fd2425e40aa71bc75782f3a3e58a573bee6bdded1956c27d7dc8d5885da7a8c8c499cb1451ca95743b4291cec96f72c7b374b7b2546c20bd611bcb97084b8590
7
+ data.tar.gz: b517dcecc7026d9a493f2b5d69da31edbcb5775a2a05d79cd3d169092eb46e68ef0754da0316e2bcf1b147ab9ffe0dbf6841d9cb49b95128d72c18b648a6e8bc
data/.gitattributes ADDED
@@ -0,0 +1,2 @@
1
+ # Require \n style line endings
2
+ * text eol=lf
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ .*.swp
2
+ Gemfile.lock
3
+ pkg
4
+ .yardoc
5
+ doc
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ rvm:
2
+ - 2.1.7
3
+ script:
4
+ - rspec spec
5
+ notifications:
6
+ email:
7
+ - ebooks@mispy.me
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in foxdear_ebooks.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Jaiden Mispy
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,157 @@
1
+ # twitter\_ebooks
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/foxdear_ebooks.svg)](http://badge.fury.io/rb/foxdear_ebooks)
4
+ [![Build Status](https://travis-ci.org/mispy/foxdear_ebooks.svg)](https://travis-ci.org/mispy/foxdear_ebooks)
5
+ [![Dependency Status](https://gemnasium.com/mispy/foxdear_ebooks.svg)](https://gemnasium.com/mispy/foxdear_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
+ - About 80% less memory and storage use for models
12
+ - Bots run in their own threads (no eventmachine), and startup is parallelized
13
+ - Bots start with `ebooks start`, and no longer die on unhandled exceptions
14
+ - `ebooks auth` command will create new access tokens, for running multiple bots
15
+ - `ebooks console` starts a ruby interpreter with bots loaded (see Ebooks::Bot.all)
16
+ - Replies are slightly rate-limited to prevent infinite bot convos
17
+ - Non-participating users in a mention chain will be dropped after a few tweets
18
+ - [API documentation](http://rdoc.info/github/mispy/foxdear_ebooks) and tests
19
+
20
+ Note that 3.0 is not backwards compatible with 2.x, so upgrade carefully! In particular, **make sure to regenerate your models** since the storage format changed.
21
+
22
+ ## Installation
23
+
24
+ Requires Ruby 2.1+. Ruby 2.3+ is recommended.
25
+
26
+ ```bash
27
+ gem install foxdear_ebooks
28
+ ```
29
+
30
+ ## Setting up a bot
31
+
32
+ Run `ebooks new <reponame>` to generate a new repository containing a sample bots.rb file, which looks like this:
33
+
34
+ ``` ruby
35
+ # This is an example bot definition with event handlers commented out
36
+ # You can define and instantiate as many bots as you like
37
+
38
+ class MyBot < Ebooks::Bot
39
+ # Configuration here applies to all MyBots
40
+ def configure
41
+ # Consumer details come from registering an app at https://dev.twitter.com/
42
+ # Once you have consumer details, use "ebooks auth" for new access tokens
43
+ self.consumer_key = "" # Your app consumer key
44
+ self.consumer_secret = "" # Your app consumer secret
45
+
46
+ # Users to block instead of interacting with
47
+ self.blacklist = ['tnietzschequote']
48
+
49
+ # Range in seconds to randomize delay when bot.delay is called
50
+ self.delay_range = 1..6
51
+ end
52
+
53
+ def on_startup
54
+ scheduler.every '24h' do
55
+ # Tweet something every 24 hours
56
+ # See https://github.com/jmettraux/rufus-scheduler
57
+ # tweet("hi")
58
+ # pictweet("hi", "cuteselfie.jpg")
59
+ end
60
+ end
61
+
62
+ def on_message(dm)
63
+ # Reply to a DM
64
+ # reply(dm, "secret secrets")
65
+ end
66
+
67
+ def on_follow(user)
68
+ # Follow a user back
69
+ # follow(user.screen_name)
70
+ end
71
+
72
+ def on_mention(tweet)
73
+ # Reply to a mention
74
+ # reply(tweet, meta(tweet).reply_prefix + "oh hullo")
75
+ end
76
+
77
+ def on_timeline(tweet)
78
+ # Reply to a tweet in the bot's timeline
79
+ # reply(tweet, meta(tweet).reply_prefix + "nice tweet")
80
+ end
81
+
82
+ def on_favorite(user, tweet)
83
+ # Follow user who just favorited bot's tweet
84
+ # follow(user.screen_name)
85
+ end
86
+
87
+ def on_retweet(tweet)
88
+ # Follow user who just retweeted bot's tweet
89
+ # follow(tweet.user.screen_name)
90
+ end
91
+ end
92
+
93
+ # Make a MyBot and attach it to an account
94
+ MyBot.new("abby_ebooks") do |bot|
95
+ bot.access_token = "" # Token connecting the app to this account
96
+ bot.access_token_secret = "" # Secret connecting the app to this account
97
+ end
98
+ ```
99
+
100
+ `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.
101
+
102
+ 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.
103
+
104
+ ## Archiving accounts
105
+
106
+ twitter\_ebooks comes with a syncing tool to download and then incrementally update a local json archive of a user's tweets (in this case, my good friend @0xabad1dea):
107
+
108
+ ``` zsh
109
+ ➜ ebooks archive 0xabad1dea corpus/0xabad1dea.json
110
+ Currently 20209 tweets for 0xabad1dea
111
+ Received 67 new tweets
112
+ ```
113
+
114
+ The first time you'll run this, it'll ask for auth details to connect with. Due to API limitations, for users with high numbers of tweets it may not be possible to get their entire history in the initial download. However, so long as you run it frequently enough you can maintain a perfect copy indefinitely into the future.
115
+
116
+ ## Text models
117
+
118
+ In order to use the included text modeling, you'll first need to preprocess your archive into a more efficient form:
119
+
120
+ ``` zsh
121
+ ➜ ebooks consume corpus/0xabad1dea.json
122
+ Reading json corpus from corpus/0xabad1dea.json
123
+ Removing commented lines and sorting mentions
124
+ Segmenting text into sentences
125
+ Tokenizing 7075 statements and 17947 mentions
126
+ Ranking keywords
127
+ Corpus consumed to model/0xabad1dea.model
128
+ ```
129
+
130
+ Notably, this works with both json tweet archives and plaintext files (based on file extension), so you can make a model out of any kind of text.
131
+
132
+ Text files use newlines and full stops to seperate statements.
133
+
134
+ Once you have a model, the primary use is to produce statements and related responses to input, using a pseudo-Markov generator:
135
+
136
+ ``` ruby
137
+ > model = Ebooks::Model.load("model/0xabad1dea.model")
138
+ > model.make_statement(280)
139
+ => "My Terrible Netbook may be the kind of person who buys Starbucks, but this Rackspace vuln is pretty straight up a backdoor"
140
+ > model.make_response("The NSA is coming!", 130)
141
+ => "Hey - someone who claims to be an NSA conspiracy"
142
+ ```
143
+
144
+ 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:
145
+
146
+ ``` ruby
147
+ top100 = model.keywords.take(100)
148
+ tokens = Ebooks::NLP.tokenize(tweet.text)
149
+
150
+ if tokens.find { |t| top100.include?(t) }
151
+ favorite(tweet)
152
+ end
153
+ ```
154
+
155
+ ## Bot niceness
156
+
157
+ foxdear_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/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
data/bin/fdebooks ADDED
@@ -0,0 +1,454 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ require 'foxdear_ebooks'
5
+ require 'ostruct'
6
+ require 'fileutils'
7
+
8
+ module Ebooks::Util
9
+ def pretty_exception(e)
10
+
11
+ end
12
+ end
13
+
14
+ module Ebooks::CLI
15
+ APP_PATH = Dir.pwd # XXX do some recursive thing instead
16
+ HELP = OpenStruct.new
17
+
18
+ HELP.default = <<STR
19
+ Usage:
20
+ ebooks help <command>
21
+
22
+ ebooks new <reponame>
23
+ ebooks s[tart]
24
+ ebooks c[onsole]
25
+ ebooks auth
26
+ ebooks consume <corpus_path> [corpus_path2] [...]
27
+ ebooks consume-all <model_name> <corpus_path> [corpus_path2] [...]
28
+ ebooks append <model_name> <corpus_path>
29
+ ebooks gen <model_path> [input]
30
+ ebooks archive <username> [path]
31
+ ebooks sync <botname> [username]
32
+ ebooks tweet <model_path> <botname>
33
+ ebooks version
34
+ STR
35
+
36
+ def self.help(command=nil)
37
+ if command.nil?
38
+ log HELP.default
39
+ else
40
+ log HELP[command].gsub(/^ {4}/, '')
41
+ end
42
+ end
43
+
44
+ HELP.new = <<-STR
45
+ Usage: ebooks new <reponame>
46
+
47
+ Creates a new skeleton repository defining a template bot in
48
+ the current working directory specified by <reponame>.
49
+ STR
50
+
51
+ def self.new(reponame)
52
+ if reponame.nil?
53
+ help :new
54
+ exit 1
55
+ end
56
+
57
+ path = "./#{reponame}"
58
+
59
+ if File.exists?(path)
60
+ log "#{path} already exists. Please remove if you want to recreate."
61
+ exit 1
62
+ end
63
+
64
+ FileUtils.cp_r(Ebooks::SKELETON_PATH, path)
65
+ FileUtils.mv(File.join(path, 'gitignore'), File.join(path, '.gitignore'))
66
+
67
+ File.open(File.join(path, 'bots.rb'), 'w') do |f|
68
+ template = File.read(File.join(Ebooks::SKELETON_PATH, 'bots.rb'))
69
+ f.write(template.gsub("{{BOT_NAME}}", reponame))
70
+ end
71
+
72
+ File.open(File.join(path, 'Gemfile'), 'w') do |f|
73
+ template = File.read(File.join(Ebooks::SKELETON_PATH, 'Gemfile'))
74
+ f.write(template.gsub("{{RUBY_VERSION}}", RUBY_VERSION))
75
+ end
76
+
77
+ log "New foxdear_ebooks app created at #{reponame}"
78
+ end
79
+
80
+ HELP.consume = <<-STR
81
+ Usage: ebooks consume <corpus_path> [corpus_path2] [...]
82
+
83
+ Processes some number of text files or json tweet corpuses
84
+ into usable models. These will be output at model/<corpus_name>.model
85
+ STR
86
+
87
+ def self.consume(pathes)
88
+ if pathes.empty?
89
+ help :consume
90
+ exit 1
91
+ end
92
+
93
+ pathes.each do |path|
94
+ filename = File.basename(path)
95
+ shortname = filename.split('.')[0..-2].join('.')
96
+
97
+ FileUtils.mkdir_p(File.join(APP_PATH, 'model'))
98
+ outpath = File.join(APP_PATH, 'model', "#{shortname}.model")
99
+
100
+ Ebooks::Model.consume(path).save(outpath)
101
+ log "Corpus consumed to #{outpath}"
102
+ end
103
+ end
104
+
105
+ HELP.consume_all = <<-STR
106
+ Usage: ebooks consume-all <model_name> <corpus_path> [corpus_path2] [...]
107
+
108
+ Processes some number of text files or json tweet corpuses
109
+ into one usable model. It will be output at model/<model_name>.model
110
+ STR
111
+
112
+ def self.consume_all(name, paths)
113
+ if paths.empty?
114
+ help :consume_all
115
+ exit 1
116
+ end
117
+
118
+ outpath = File.join(APP_PATH, 'model', "#{name}.model")
119
+ Ebooks::Model.consume_all(paths).save(outpath)
120
+ log "Corpuses consumed to #{outpath}"
121
+ end
122
+
123
+ HELP.append = <<-STR
124
+ Usage: ebooks append <model_name> <corpus_path>
125
+
126
+ Process then append the provided corpus to the model
127
+ instead of overwriting.
128
+ STR
129
+
130
+ def self.append(name, path)
131
+ if !name || !path
132
+ help :append
133
+ exit 1
134
+ end
135
+
136
+ Ebooks::Model.consume(path).append(File.join(APP_PATH,'model',"#{name}.model"))
137
+ log "Corpus appended to #{name}.model"
138
+ end
139
+
140
+
141
+ HELP.jsonify = <<-STR
142
+ Usage: ebooks jsonify <tweets.csv> [tweets.csv2] [...]
143
+
144
+ Takes a csv twitter archive and converts it to json.
145
+ STR
146
+
147
+ def self.jsonify(paths)
148
+ if paths.empty?
149
+ log usage
150
+ exit
151
+ end
152
+
153
+ paths.each do |path|
154
+ name = File.basename(path).split('.')[0]
155
+ new_path = name + ".json"
156
+
157
+ tweets = []
158
+ id = nil
159
+ if path.split('.')[-1] == "csv" #from twitter archive
160
+ csv_archive = CSV.read(path, :headers=>:first_row)
161
+ tweets = csv_archive.map do |tweet|
162
+ { text: tweet['text'], id: tweet['tweet_id'] }
163
+ end
164
+ else
165
+ File.read(path).split("\n").each do |l|
166
+ if l.start_with?('# ')
167
+ id = l.split('# ')[-1]
168
+ else
169
+ tweet = { text: l }
170
+ if id
171
+ tweet[:id] = id
172
+ id = nil
173
+ end
174
+ tweets << tweet
175
+ end
176
+ end
177
+ end
178
+
179
+ File.open(new_path, 'w') do |f|
180
+ log "Writing #{tweets.length} tweets to #{new_path}"
181
+ f.write(JSON.pretty_generate(tweets))
182
+ end
183
+ end
184
+ end
185
+
186
+
187
+ HELP.gen = <<-STR
188
+ Usage: ebooks gen <model_path> [input]
189
+
190
+ Make a test tweet from the processed model at <model_path>.
191
+ Will respond to input if provided.
192
+ STR
193
+
194
+ def self.gen(model_path, input)
195
+ if model_path.nil?
196
+ help :gen
197
+ exit 1
198
+ end
199
+
200
+ model = Ebooks::Model.load(model_path)
201
+ if input && !input.empty?
202
+ puts "@cmd " + model.make_response(input, 135)
203
+ else
204
+ puts model.make_statement
205
+ end
206
+ end
207
+
208
+ HELP.archive = <<-STR
209
+ Usage: ebooks archive <username> [outpath]
210
+
211
+ Downloads a json corpus of the <username>'s tweets.
212
+ Output defaults to corpus/<username>.json
213
+ Due to API limitations, this can only receive up to ~3000 tweets
214
+ into the past.
215
+
216
+ The first time you run archive, you will need to enter the auth
217
+ details of some account to use for accessing the API. This info
218
+ will then be stored in ~/.ebooksrc for later use, and can be
219
+ modified there if needed.
220
+ STR
221
+
222
+ def self.archive(username, outpath=nil)
223
+ if username.nil?
224
+ help :archive
225
+ exit 1
226
+ end
227
+
228
+ Ebooks::Archive.new(username, outpath).sync
229
+ end
230
+
231
+ HELP.sync = <<-STR
232
+ Usage: ebooks sync <botname> <username>
233
+
234
+ Copies and flips <username>'s avatar and cover photo, uploading them to <botname>'s profile.
235
+
236
+ Stores saved avatar's and covers in image/.
237
+
238
+ STR
239
+
240
+ def self.sync(botname, username)
241
+ if botname.nil?
242
+ help :sync
243
+ exit 1
244
+ end
245
+
246
+ load File.join(APP_PATH, 'bots.rb')
247
+ Ebooks::Sync::run(botname, username)
248
+ end
249
+
250
+ HELP.tweet = <<-STR
251
+ Usage: ebooks tweet <model_path> <botname>
252
+
253
+ Sends a public tweet from the specified bot using text
254
+ from the processed model at <model_path>.
255
+ STR
256
+
257
+ def self.tweet(modelpath, botname)
258
+ if modelpath.nil? || botname.nil?
259
+ help :tweet
260
+ exit 1
261
+ end
262
+
263
+ load File.join(APP_PATH, 'bots.rb')
264
+ model = Ebooks::Model.load(modelpath)
265
+ statement = model.make_statement
266
+ bot = Ebooks::Bot.get(botname)
267
+ if bot.nil?
268
+ log "No such bot configured in bots.rb: #{botname}"
269
+ exit 1
270
+ end
271
+ bot.configure
272
+ bot.tweet(statement)
273
+ end
274
+
275
+ HELP.auth = <<-STR
276
+ Usage: ebooks auth
277
+
278
+ Authenticates your Twitter app for any account. By default, will
279
+ use the consumer key and secret from the first defined bot. You
280
+ can specify another by setting the CONSUMER_KEY and CONSUMER_SECRET
281
+ environment variables.
282
+ STR
283
+
284
+ def self.auth
285
+ consumer_key, consumer_secret = find_consumer
286
+ require 'oauth'
287
+
288
+ consumer = OAuth::Consumer.new(
289
+ consumer_key,
290
+ consumer_secret,
291
+ site: 'https://twitter.com/',
292
+ scheme: :header
293
+ )
294
+
295
+ request_token = consumer.get_request_token
296
+ auth_url = request_token.authorize_url()
297
+
298
+ pin = nil
299
+ loop do
300
+ log auth_url
301
+
302
+ log "Go to the above url and follow the prompts, then enter the PIN code here."
303
+ print "> "
304
+
305
+ pin = STDIN.gets.chomp
306
+
307
+ break unless pin.empty?
308
+ end
309
+
310
+ access_token = request_token.get_access_token(oauth_verifier: pin)
311
+
312
+ log "Account authorized successfully. Make sure to put these in your bots.rb!\n" +
313
+ " bot.access_token = \"#{access_token.token}\"\n" +
314
+ " bot.access_token_secret = \"#{access_token.secret}\""
315
+ end
316
+
317
+ HELP.console = <<-STR
318
+ Usage: ebooks c[onsole]
319
+
320
+ Starts an interactive ruby session with your bots loaded
321
+ and configured.
322
+ STR
323
+
324
+ def self.console
325
+ load_bots
326
+ require 'pry'; Ebooks.module_exec { pry }
327
+ end
328
+
329
+ HELP.version = <<-STR
330
+ Usage: ebooks version
331
+
332
+ Shows you foxdear_ebooks' version number.
333
+ STR
334
+
335
+ def self.version
336
+ require File.expand_path('../../lib/foxdear_ebooks/version', __FILE__)
337
+ log Ebooks::VERSION
338
+ end
339
+
340
+ HELP.start = <<-STR
341
+ Usage: ebooks s[tart] [botname]
342
+
343
+ Starts running bots. If botname is provided, only runs that bot.
344
+ STR
345
+
346
+ def self.start(botname=nil)
347
+ load_bots
348
+
349
+ if botname.nil?
350
+ bots = Ebooks::Bot.all
351
+ else
352
+ bots = Ebooks::Bot.all.select { |bot| bot.username == botname }
353
+ if bots.empty?
354
+ log "Couldn't find a defined bot for @#{botname}!"
355
+ exit 1
356
+ end
357
+ end
358
+
359
+ threads = []
360
+ bots.each do |bot|
361
+ threads << Thread.new { bot.prepare }
362
+ end
363
+ threads.each(&:join)
364
+
365
+ threads = []
366
+ bots.each do |bot|
367
+ threads << Thread.new do
368
+ loop do
369
+ begin
370
+ bot.start
371
+ rescue Exception => e
372
+ bot.log e.inspect
373
+ puts e.backtrace.map { |s| "\t"+s }.join("\n")
374
+ end
375
+ bot.log "Sleeping before reconnect"
376
+ sleep 60
377
+ end
378
+ end
379
+ end
380
+ threads.each(&:join)
381
+ end
382
+
383
+ # Non-command methods
384
+
385
+ def self.find_consumer
386
+ if ENV['CONSUMER_KEY'] && ENV['CONSUMER_SECRET']
387
+ log "Using consumer details from environment variables:\n" +
388
+ " consumer key: #{ENV['CONSUMER_KEY']}\n" +
389
+ " consumer secret: #{ENV['CONSUMER_SECRET']}"
390
+ return [ENV['CONSUMER_KEY'], ENV['CONSUMER_SECRET']]
391
+ end
392
+
393
+ load_bots
394
+ consumer_key = nil
395
+ consumer_secret = nil
396
+ Ebooks::Bot.all.each do |bot|
397
+ if bot.consumer_key && bot.consumer_secret
398
+ consumer_key = bot.consumer_key
399
+ consumer_secret = bot.consumer_secret
400
+ log "Using consumer details from @#{bot.username}:\n" +
401
+ " consumer key: #{bot.consumer_key}\n" +
402
+ " consumer secret: #{bot.consumer_secret}\n"
403
+ return consumer_key, consumer_secret
404
+ end
405
+ end
406
+
407
+ if consumer_key.nil? || consumer_secret.nil?
408
+ log "Couldn't find any consumer details to auth an account with.\n" +
409
+ "Please either configure a bot with consumer_key and consumer_secret\n" +
410
+ "or provide the CONSUMER_KEY and CONSUMER_SECRET environment variables."
411
+ exit 1
412
+ end
413
+ end
414
+
415
+ def self.load_bots
416
+ load 'bots.rb'
417
+
418
+ if Ebooks::Bot.all.empty?
419
+ puts "Couldn't find any bots! Please make sure bots.rb instantiates at least one bot."
420
+ end
421
+ end
422
+
423
+ def self.command(args)
424
+ if args.length == 0
425
+ help
426
+ exit 1
427
+ end
428
+
429
+ case args[0]
430
+ when "new" then new(args[1])
431
+ when "consume" then consume(args[1..-1])
432
+ when "consume-all" then consume_all(args[1], args[2..-1])
433
+ when "append" then append(args[1],args[2])
434
+ when "gen" then gen(args[1], args[2..-1].join(' '))
435
+ when "archive" then archive(args[1], args[2])
436
+ when "sync" then sync(args[1], args[2])
437
+ when "tweet" then tweet(args[1], args[2])
438
+ when "jsonify" then jsonify(args[1..-1])
439
+ when "auth" then auth
440
+ when "console" then console
441
+ when "c" then console
442
+ when "start" then start(args[1])
443
+ when "s" then start(args[1])
444
+ when "help" then help(args[1])
445
+ when "version" then version
446
+ else
447
+ log "No such command '#{args[0]}'"
448
+ help
449
+ exit 1
450
+ end
451
+ end
452
+ end
453
+
454
+ Ebooks::CLI.command(ARGV)