bot_twitter_ebooks 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 01cb997f239d1bc64a15b0ccbc6b1e83d641c9fd
4
+ data.tar.gz: f09849a1882a75c3c32f4cd0c6811c6c40a4968f
5
+ SHA512:
6
+ metadata.gz: e8ca65ed4046b3665a5d5675efa37643cafda354b953d935aa9e3ef2d2c4399f437d10deb337db8e6545e96afc3b48467be9c23e53b42c3786ca3b8997c4ef39
7
+ data.tar.gz: 8694e98af0cf9f66d627c62ae33206ea387cfae51e40a414eace37818fce90d70c673dea2f1f86c82607d6073052e3adedf2953f194556297e50eb0cffe3b6db
@@ -0,0 +1 @@
1
+ * text=auto
@@ -0,0 +1,200 @@
1
+
2
+ # Created by https://www.gitignore.io/api/git,vim,ruby,linux,macos,emacs,windows
3
+
4
+ ### Emacs ###
5
+ # -*- mode: gitignore; -*-
6
+ *~
7
+ \#*\#
8
+ /.emacs.desktop
9
+ /.emacs.desktop.lock
10
+ *.elc
11
+ auto-save-list
12
+ tramp
13
+ .\#*
14
+
15
+ # Org-mode
16
+ .org-id-locations
17
+ *_archive
18
+
19
+ # flymake-mode
20
+ *_flymake.*
21
+
22
+ # eshell files
23
+ /eshell/history
24
+ /eshell/lastdir
25
+
26
+ # elpa packages
27
+ /elpa/
28
+
29
+ # reftex files
30
+ *.rel
31
+
32
+ # AUCTeX auto folder
33
+ /auto/
34
+
35
+ # cask packages
36
+ .cask/
37
+ dist/
38
+
39
+ # Flycheck
40
+ flycheck_*.el
41
+
42
+ # server auth directory
43
+ /server/
44
+
45
+ # projectiles files
46
+ .projectile
47
+ projectile-bookmarks.eld
48
+
49
+ # directory configuration
50
+ .dir-locals.el
51
+
52
+ # saveplace
53
+ places
54
+
55
+ # url cache
56
+ url/cache/
57
+
58
+ # cedet
59
+ ede-projects.el
60
+
61
+ # smex
62
+ smex-items
63
+
64
+ # company-statistics
65
+ company-statistics-cache.el
66
+
67
+ # anaconda-mode
68
+ anaconda-mode/
69
+
70
+ ### Git ###
71
+ *.orig
72
+
73
+ ### Linux ###
74
+
75
+ # temporary files which can be created if a process still has a handle open of a deleted file
76
+ .fuse_hidden*
77
+
78
+ # KDE directory preferences
79
+ .directory
80
+
81
+ # Linux trash folder which might appear on any partition or disk
82
+ .Trash-*
83
+
84
+ # .nfs files are created when an open file is removed but is still being accessed
85
+ .nfs*
86
+
87
+ ### macOS ###
88
+ *.DS_Store
89
+ .AppleDouble
90
+ .LSOverride
91
+
92
+ # Icon must end with two \r
93
+ Icon
94
+
95
+ # Thumbnails
96
+ ._*
97
+
98
+ # Files that might appear in the root of a volume
99
+ .DocumentRevisions-V100
100
+ .fseventsd
101
+ .Spotlight-V100
102
+ .TemporaryItems
103
+ .Trashes
104
+ .VolumeIcon.icns
105
+ .com.apple.timemachine.donotpresent
106
+
107
+ # Directories potentially created on remote AFP share
108
+ .AppleDB
109
+ .AppleDesktop
110
+ Network Trash Folder
111
+ Temporary Items
112
+ .apdisk
113
+
114
+ ### Ruby ###
115
+ *.gem
116
+ *.rbc
117
+ /.config
118
+ /coverage/
119
+ /InstalledFiles
120
+ /pkg/
121
+ /spec/reports/
122
+ /spec/examples.txt
123
+ /test/tmp/
124
+ /test/version_tmp/
125
+ /tmp/
126
+
127
+ # Used by dotenv library to load environment variables.
128
+ .env
129
+
130
+ ## Specific to RubyMotion:
131
+ .dat*
132
+ .repl_history
133
+ build/
134
+ *.bridgesupport
135
+ build-iPhoneOS/
136
+ build-iPhoneSimulator/
137
+
138
+ ## Specific to RubyMotion (use of CocoaPods):
139
+ #
140
+ # We recommend against adding the Pods directory to your .gitignore. However
141
+ # you should judge for yourself, the pros and cons are mentioned at:
142
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
143
+ #
144
+ # vendor/Pods/
145
+
146
+ ## Documentation cache and generated files:
147
+ /.yardoc/
148
+ /_yardoc/
149
+ /doc/
150
+ /rdoc/
151
+
152
+ ## Environment normalization:
153
+ /.bundle/
154
+ /vendor/bundle
155
+ /lib/bundler/man/
156
+
157
+ # for a library or gem, you might want to ignore these files since the code is
158
+ # intended to run in multiple environments; otherwise, check them in:
159
+ Gemfile.lock
160
+ .ruby-version
161
+ .ruby-gemset
162
+
163
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
164
+ .rvmrc
165
+
166
+ ### Vim ###
167
+ # swap
168
+ [._]*.s[a-v][a-z]
169
+ [._]*.sw[a-p]
170
+ [._]s[a-v][a-z]
171
+ [._]sw[a-p]
172
+ # session
173
+ Session.vim
174
+ # temporary
175
+ .netrwhist
176
+ # auto-generated tag files
177
+ tags
178
+
179
+ ### Windows ###
180
+ # Windows thumbnail cache files
181
+ Thumbs.db
182
+ ehthumbs.db
183
+ ehthumbs_vista.db
184
+
185
+ # Folder config file
186
+ Desktop.ini
187
+
188
+ # Recycle Bin used on file shares
189
+ $RECYCLE.BIN/
190
+
191
+ # Windows Installer files
192
+ *.cab
193
+ *.msi
194
+ *.msm
195
+ *.msp
196
+
197
+ # Windows shortcuts
198
+ *.lnk
199
+
200
+ # End of https://www.gitignore.io/api/git,vim,ruby,linux,macos,emacs,windows
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,7 @@
1
+ rvm:
2
+ - 2.4.2
3
+ script:
4
+ - rspec spec
5
+ notifications:
6
+ email:
7
+ - astro@astrolince.com
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in bot_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.
@@ -0,0 +1,168 @@
1
+ # bot_twitter_ebooks
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/bot_twitter_ebooks.svg)](http://badge.fury.io/rb/bot_twitter_ebooks)
4
+ [![Build Status](https://travis-ci.org/astrolince/bot_twitter_ebooks.svg)](https://travis-ci.org/astrolince/bot_twitter_ebooks)
5
+ [![Dependency Status](https://gemnasium.com/astrolince/bot_twitter_ebooks.svg)](https://gemnasium.com/astrolince/bot_twitter_ebooks)
6
+
7
+ A framework for building interactive twitterbots which generate tweets based on pseudo-Markov texts models and respond to mentions/DMs/favs/rts. See [ebooks_example](https://github.com/astrolince/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/astrolince/bot_twitter_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.4+ is recommended.
25
+
26
+ ```bash
27
+ gem install bot_twitter_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
+ # Make sure to set your API permissions to "Read, Write and Access direct messages" or this won't work!
65
+ # reply(dm, "secret secrets")
66
+ end
67
+
68
+ def on_follow(user)
69
+ # Follow a user back
70
+ # follow(user.screen_name)
71
+ end
72
+
73
+ def on_mention(tweet)
74
+ # Reply to a mention
75
+ # reply(tweet, "oh hullo")
76
+ end
77
+
78
+ def on_timeline(tweet)
79
+ # Reply to a tweet in the bot's timeline
80
+ # reply(tweet, "nice tweet")
81
+ end
82
+
83
+ def on_favorite(user, tweet)
84
+ # Follow user who just favorited bot's tweet
85
+ # follow(user.screen_name)
86
+ end
87
+
88
+ def on_retweet(tweet)
89
+ # Follow user who just retweeted bot's tweet
90
+ # follow(tweet.user.screen_name)
91
+ end
92
+ end
93
+
94
+ # Make a MyBot and attach it to an account
95
+ MyBot.new("elonmusk_ebooks") do |bot|
96
+ bot.access_token = "" # Token connecting the app to this account
97
+ bot.access_token_secret = "" # Secret connecting the app to this account
98
+ end
99
+ ```
100
+
101
+ `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.
102
+
103
+ 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.
104
+
105
+ ## Archiving accounts
106
+
107
+ bot_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 @elonmusk):
108
+
109
+ ``` zsh
110
+ ➜ ebooks archive elonmusk corpus/elonmusk.json
111
+ Currently 2584 tweets for elonmusk
112
+ Received 34 new tweets
113
+ ```
114
+
115
+ 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.
116
+
117
+ If you have full access to the account you can request your full Twitter archive in web settings and convert the tweets.csv to .json with the command: `ebooks jsonify tweets.csv`
118
+
119
+ ## Text models
120
+
121
+ In order to use the included text modeling, you'll first need to preprocess your archive into a more efficient form:
122
+
123
+ ``` zsh
124
+ ➜ ebooks consume corpus/elonmusk.json
125
+ Reading json corpus from corpus/elonmusk.json
126
+ Removing commented lines and sorting mentions
127
+ Segmenting text into sentences
128
+ Tokenizing 987 statements and 1597 mentions
129
+ Ranking keywords
130
+ Corpus consumed to model/elonmusk.model
131
+ ```
132
+
133
+ 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.
134
+
135
+ Text files use newlines and full stops to seperate statements.
136
+
137
+ You can also consume multiple archives / plaintext files into a single model using `ebooks consume-all <model_name> <corpus_paths>`.
138
+
139
+ Once you have a model, the primary use is to produce statements and related responses to input, using a pseudo-Markov generator:
140
+
141
+ ``` ruby
142
+ > model = Ebooks::Model.load("model/elonmusk.model")
143
+ > model.make_statement(140)
144
+ => "Rainbows, unicorns and LA and seems to be working on the Falcon fireball investigation."
145
+ > model.make_response("Will you give free trips to Mars?", 130)
146
+ => "Plan is to provide something special that only existing owners can give to friends and it is limited to 5 people."
147
+ ```
148
+
149
+ 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:
150
+
151
+ ``` ruby
152
+ top100 = model.keywords.take(100)
153
+ tokens = Ebooks::NLP.tokenize(tweet.text)
154
+
155
+ if tokens.find { |t| top100.include?(t) }
156
+ favorite(tweet)
157
+ end
158
+ ```
159
+
160
+ ## Bot niceness
161
+
162
+ bot_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!
163
+
164
+ ## License
165
+
166
+ bot_twitter_ebooks is [MIT licensed](https://github.com/astrolince/bot_twitter_ebooks/blob/master/LICENSE) and is a fork of [twitter_ebooks](https://github.com/mispy/twitter_ebooks).
167
+
168
+ Thanks to Jaiden Mispy ([@mispy](https://github.com/mispy)) and all the human beings/bots/star stuff affected by universe entropy that contribute to the project.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,454 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ require 'bot_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
+ 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 twitter_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'].to_i }
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 twitter_ebooks' version number.
333
+ STR
334
+
335
+ def self.version
336
+ require File.expand_path('../../lib/twitter_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)