twitter_ebooks_poll 3.2.0

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