chewbranca-twibot 0.1.7.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,101 @@
1
+ module Twibot
2
+ @@prompt = false
3
+
4
+ def self.prompt=(p)
5
+ @@prompt = f
6
+ end
7
+
8
+ module Macros
9
+ def self.included(mod)
10
+ @@bot = nil
11
+ end
12
+
13
+ def configure(&blk)
14
+ bot.configure(&blk)
15
+ end
16
+
17
+ def message(pattern = nil, options = {}, &blk)
18
+ add_handler(:message, pattern, options, &blk)
19
+ end
20
+
21
+ def reply(pattern = nil, options = {}, &blk)
22
+ add_handler(:reply, pattern, options, &blk)
23
+ end
24
+
25
+ def tweet(pattern = nil, options = {}, &blk)
26
+ add_handler(:tweet, pattern, options, &blk)
27
+ end
28
+
29
+ def follower(&blk)
30
+ add_handler(:follower, nil, {}, &blk)
31
+ end
32
+
33
+ def hashtag(tag_or_tags, pattern = nil, options = {}, &blk)
34
+ query = [tag_or_tags].flatten.map {|ht| ht.to_s[0] == ?# ? ht.to_s : "##{ht}"}.join(" OR ")
35
+ add_handler([:search, query], pattern, options, &blk)
36
+ end
37
+ alias_method :hashtags, :hashtag
38
+
39
+ def search(query, pattern = nil, options = {}, &blk)
40
+ add_handler([:search, query], pattern, options, &blk)
41
+ end
42
+
43
+ def after(event=:all, &blk)
44
+ add_hook :"after_#{event}", &blk
45
+ end
46
+
47
+ def before(event=:all, &blk)
48
+ add_hook :"before_#{event}", &blk
49
+ end
50
+
51
+ def twitter
52
+ bot.twitter
53
+ end
54
+
55
+ alias_method :client, :twitter
56
+
57
+ def post_tweet(msg)
58
+ message = msg.respond_to?(:text) ? msg.text : msg
59
+ puts message
60
+ client.status(:post, message)
61
+ end
62
+
63
+ def post_reply(status, msg)
64
+ text = msg.respond_to?(:text) ? msg.text : msg
65
+ reply_to_screen_name = status.user.screen_name
66
+ reply_to_status_id = status.id
67
+ message = "@#{reply_to_screen_name} #{text}"
68
+ puts message
69
+ client.status(:reply, message, reply_to_status_id)
70
+ end
71
+
72
+ def run?
73
+ !@@bot.nil?
74
+ end
75
+
76
+ private
77
+ def add_handler(type, pattern, options, &blk)
78
+ bot.add_handler(type, Twibot::Handler.new(pattern, options, &blk))
79
+ end
80
+
81
+ def add_hook(hook, &blk)
82
+ bot.add_hook(hook, &blk)
83
+ end
84
+
85
+ def bot
86
+ return @@bot unless @@bot.nil?
87
+
88
+ begin
89
+ @@bot = Twibot::Bot.new nil, true
90
+ rescue Exception
91
+ @@bot = Twibot::Bot.new(Twibot::Config.default << Twibot::CliConfig.new, true)
92
+ end
93
+
94
+ @@bot
95
+ end
96
+
97
+ def self.bot=(bot)
98
+ @@bot = bot
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,4 @@
1
+ module Twibot
2
+ module Tweets
3
+ end
4
+ end
data/lib/twibot.rb ADDED
@@ -0,0 +1,87 @@
1
+ require 'time'
2
+ require 'twitter'
3
+ require 'twitter/client'
4
+ require 'yaml'
5
+ require File.join(File.dirname(__FILE__), 'hash')
6
+
7
+ module Twibot
8
+
9
+ # :stopdoc:
10
+ VERSION = '0.1.7'
11
+ LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
12
+ PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
13
+ # :startdoc:
14
+
15
+ # Returns the version string for the library.
16
+ #
17
+ def self.version
18
+ VERSION
19
+ end
20
+
21
+ # Returns the library path for the module. If any arguments are given,
22
+ # they will be joined to the end of the libray path using
23
+ # <tt>File.join</tt>.
24
+ #
25
+ def self.libpath( *args )
26
+ args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
27
+ end
28
+
29
+ # Returns the lpath for the module. If any arguments are given,
30
+ # they will be joined to the end of the path using
31
+ # <tt>File.join</tt>.
32
+ #
33
+ def self.path( *args )
34
+ args.empty? ? PATH : ::File.join(PATH, args.flatten)
35
+ end
36
+
37
+ # Utility method used to require all files ending in .rb that lie in the
38
+ # directory below this file that has the same name as the filename passed
39
+ # in. Optionally, a specific _directory_ name can be passed in such that
40
+ # the _filename_ does not have to be equivalent to the directory.
41
+ #
42
+ def self.require_all_libs_relative_to( fname, dir = nil )
43
+ dir ||= File.basename(fname, '.*')
44
+ search_me = File.expand_path(File.join(File.dirname(fname), dir, '**', '*.rb'))
45
+ Dir.glob(search_me).sort.each {|rb| require rb }
46
+ end
47
+
48
+ @@app_file = lambda do
49
+ ignore = [
50
+ /lib\/twibot.*\.rb/, # Library
51
+ /\(.*\)/, # Generated code
52
+ /custom_require\.rb/ # RubyGems require
53
+ ]
54
+
55
+ path = caller.map { |line| line.split(/:\d/, 2).first }.find do |file|
56
+ next if ignore.any? { |pattern| file =~ pattern }
57
+ file
58
+ end
59
+
60
+ path || $0
61
+ end.call
62
+
63
+ #
64
+ # File name of the application file. Inspired by Sinatra
65
+ #
66
+ def self.app_file
67
+ @@app_file
68
+ end
69
+
70
+ #
71
+ # Runs application if application file is the script being executed
72
+ #
73
+ def self.run?
74
+ self.app_file == $0
75
+ end
76
+
77
+ end # module Twibot
78
+
79
+ Twitter::Client.configure do |config|
80
+ config.application_name = 'Twibot'
81
+ config.application_version = Twibot.version
82
+ config.application_url = 'http://github.com/cjohansen/twibot'
83
+ end
84
+
85
+ Twibot.require_all_libs_relative_to(__FILE__)
86
+
87
+ # EOF
data/test/test_bot.rb ADDED
@@ -0,0 +1,300 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper')) unless defined?(Twibot)
2
+ require 'fileutils'
3
+
4
+ class TestBot < Test::Unit::TestCase
5
+ should "not raise errors when initialized" do
6
+ assert_nothing_raised do
7
+ Twibot::Bot.new Twibot::Config.new
8
+ end
9
+ end
10
+
11
+ should "raise errors when initialized without config file" do
12
+ assert_raise SystemExit do
13
+ Twibot::Bot.new
14
+ end
15
+ end
16
+
17
+ should "not raise error on initialize when config file exists" do
18
+ if File.exists?("config")
19
+ FileUtils.rm("config/bot.yml")
20
+ else
21
+ FileUtils.mkdir("config")
22
+ end
23
+
24
+ File.open("config/bot.yml", "w") { |f| f.puts "" }
25
+
26
+ assert_nothing_raised do
27
+ Twibot::Bot.new
28
+ end
29
+
30
+ FileUtils.rm_rf("config")
31
+ end
32
+
33
+ should "provide configuration settings as methods" do
34
+ bot = Twibot::Bot.new Twibot::Config.new(:max_interval => 3)
35
+ assert_equal 3, bot.max_interval
36
+ end
37
+
38
+ should "return logger instance" do
39
+ bot = Twibot::Bot.new(Twibot::Config.default << Twibot::Config.new)
40
+ assert bot.log.is_a?(Logger)
41
+ end
42
+
43
+ should "respect configured log level" do
44
+ bot = Twibot::Bot.new(Twibot::Config.new(:log_level => "info"))
45
+ assert_equal Logger::INFO, bot.log.level
46
+
47
+ bot = Twibot::Bot.new(Twibot::Config.new(:log_level => "warn"))
48
+ assert_equal Logger::WARN, bot.log.level
49
+ end
50
+
51
+ should "should return false from receive without handlers" do
52
+ bot = Twibot::Bot.new(Twibot::Config.new)
53
+ assert !bot.receive_messages
54
+ assert !bot.receive_replies
55
+ assert !bot.receive_tweets
56
+ end
57
+
58
+ context "with the process option specified" do
59
+ setup do
60
+ @bot = Twibot::Bot.new(@config = Twibot::Config.default)
61
+ @bot.stubs(:prompt?).returns(false)
62
+ @bot.stubs(:twitter).returns(stub)
63
+ @bot.stubs(:processed).returns(stub)
64
+
65
+ # stop Bot actually starting during tests
66
+ @bot.stubs(:poll)
67
+ end
68
+
69
+ should "not process tweets prior to bot launch if :process option is set to :new" do
70
+ @bot.stubs(:handlers).returns({:tweet => [stub], :reply => []})
71
+
72
+ # Should fetch the latest ID for both messages and tweets
73
+ @bot.twitter.expects(:messages).with(:received, { :count => 1 }).
74
+ returns([stub(:id => (message_id = stub))]).once
75
+ @bot.twitter.expects(:timeline_for).with(:public, { :count => 1 }).
76
+ returns([stub(:id => (tweet_id = stub))]).once
77
+
78
+ # And set them to the since_id value to be used for future polling
79
+ @bot.processed.expects(:[]=).with(:message, message_id)
80
+ @bot.processed.expects(:[]=).with(:tweet, tweet_id)
81
+ @bot.processed.expects(:[]=).with(:reply, tweet_id)
82
+
83
+ @bot.configure { |c| c.process = :new }
84
+ @bot.run!
85
+ end
86
+
87
+ [:all, nil].each do |value|
88
+ should "process all tweets if :process option is set to #{value.inspect}" do
89
+ @bot.twitter.expects(:messages).never
90
+ @bot.twitter.expects(:timeline_for).never
91
+
92
+ # Shout not set the any value for the since_id tweets
93
+ @bot.processed.expects(:[]=).never
94
+
95
+ @bot.configure { |c| c.process = value }
96
+ @bot.run!
97
+ end
98
+ end
99
+
100
+ should "process all tweets after the ID specified in the :process option" do
101
+ tweet_id = 12345
102
+
103
+ @bot.processed.expects(:[]=).with(anything, 12345).times(3)
104
+
105
+ @bot.configure { |c| c.process = tweet_id }
106
+ @bot.run!
107
+ end
108
+
109
+ should "raise exit when the :process option is not recognized" do
110
+ @bot.configure { |c| c.process = "something random" }
111
+ assert_raise(SystemExit) { @bot.run! }
112
+ end
113
+
114
+ end
115
+
116
+ should "receive message" do
117
+ bot = Twibot::Bot.new(Twibot::Config.new(:log_level => "error"))
118
+ bot.add_handler(:message, Twibot::Handler.new)
119
+ bot.twitter.expects(:messages).with(:received, {}).returns([twitter_message("cjno", "Hei der!")])
120
+
121
+ assert bot.receive_messages
122
+ end
123
+
124
+ should "remember last received message" do
125
+ bot = Twibot::Bot.new(Twibot::Config.new(:log_level => "error"))
126
+ bot.add_handler(:message, Twibot::Handler.new)
127
+ bot.twitter.expects(:messages).with(:received, {}).returns([twitter_message("cjno", "Hei der!")])
128
+ assert_equal 1, bot.receive_messages
129
+
130
+ bot.twitter.expects(:messages).with(:received, { :since_id => 1 }).returns([])
131
+ assert_equal 0, bot.receive_messages
132
+ end
133
+
134
+ should "receive tweet" do
135
+ bot = Twibot::Bot.new(Twibot::Config.new(:log_level => "error"))
136
+ bot.add_handler(:tweet, Twibot::Handler.new)
137
+ bot.twitter.expects(:timeline_for).with(:public, {}).returns([tweet("cjno", "Hei der!")])
138
+
139
+ assert_equal 1, bot.receive_tweets
140
+ end
141
+
142
+ should "receive friend tweets if configured" do
143
+ bot = Twibot::Bot.new(Twibot::Config.new({:log_level => "error", :timeline_for => :friends}))
144
+ bot.add_handler(:tweet, Twibot::Handler.new)
145
+ bot.twitter.expects(:timeline_for).with(:friends, {}).returns([tweet("cjno", "Hei der!")])
146
+
147
+ assert_equal 1, bot.receive_tweets
148
+ end
149
+
150
+ should "remember received tweets" do
151
+ bot = Twibot::Bot.new(Twibot::Config.new(:log_level => "error"))
152
+ bot.add_handler(:tweet, Twibot::Handler.new)
153
+ bot.twitter.expects(:timeline_for).with(:public, {}).returns([tweet("cjno", "Hei der!")])
154
+ assert_equal 1, bot.receive_tweets
155
+
156
+ bot.twitter.expects(:timeline_for).with(:public, { :since_id => 1 }).returns([])
157
+ assert_equal 0, bot.receive_tweets
158
+ end
159
+
160
+ should "receive reply when tweet starts with login" do
161
+ bot = Twibot::Bot.new(Twibot::Config.new(:log_level => "error", :login => "irbno"))
162
+ bot.add_handler(:reply, Twibot::Handler.new)
163
+ bot.twitter.expects(:status).with(:replies, {}).returns([tweet("cjno", "@irbno Hei der!")])
164
+
165
+ assert_equal 1, bot.receive_replies
166
+ end
167
+
168
+ should "remember received replies" do
169
+ bot = Twibot::Bot.new(Twibot::Config.new(:log_level => "error", :login => "irbno"))
170
+ bot.add_handler(:reply, Twibot::Handler.new)
171
+ bot.twitter.expects(:status).with(:replies, {}).returns([tweet("cjno", "@irbno Hei der!")])
172
+ assert_equal 1, bot.receive_replies
173
+
174
+ bot.twitter.expects(:status).with(:replies, { :since_id => 1 }).returns([])
175
+ assert_equal 0, bot.receive_replies
176
+ end
177
+
178
+ should "use public as default timeline method for tweet 'verb'" do
179
+ bot = Twibot::Bot.new(Twibot::Config.default)
180
+ assert_equal :public, bot.instance_eval { @config.to_hash[:timeline_for] }
181
+ end
182
+
183
+ context "sandboxed network errors" do
184
+ should "rescue certain errors" do
185
+ bot = Twibot::Bot.new(Twibot::Config.default)
186
+
187
+ assert_nothing_raised do
188
+ bot.send(:sandbox) { raise Twitter::RESTError.new }
189
+ bot.send(:sandbox) { raise Errno::ECONNRESET.new }
190
+ bot.send(:sandbox) { raise Timeout::Error.new }
191
+ bot.send(:sandbox) { raise EOFError.new }
192
+ bot.send(:sandbox) { raise Errno::ETIMEDOUT.new }
193
+ bot.send(:sandbox) { raise JSON::ParserError.new }
194
+ bot.send(:sandbox) { raise OpenSSL::SSL::SSLError.new }
195
+ bot.send(:sandbox) { raise SystemStackError.new }
196
+ end
197
+ end
198
+
199
+ should "return default value if error is rescued" do
200
+ bot = Twibot::Bot.new(Twibot::Config.default)
201
+ assert_equal(42, bot.send(:sandbox, 42) { raise Twitter::RESTError })
202
+ end
203
+
204
+ should "not return default value when no error was raised" do
205
+ bot = Twibot::Bot.new(Twibot::Config.default)
206
+ assert_equal(65, bot.send(:sandbox, 42) { 65 })
207
+ end
208
+
209
+ should "not swallow unknown errors" do
210
+ bot = Twibot::Bot.new(Twibot::Config.default)
211
+
212
+ assert_raise StandardError do
213
+ bot.send(:sandbox) { raise StandardError.new "Oops!" }
214
+ end
215
+ end
216
+ end
217
+ end
218
+
219
+ class TestBotMacros < Test::Unit::TestCase
220
+ should "provide configure macro" do
221
+ assert respond_to?(:configure)
222
+ end
223
+
224
+ should "yield configuration" do
225
+ Twibot::Macros.bot = Twibot::Bot.new Twibot::Config.default
226
+ bot.prompt = false
227
+
228
+ conf = nil
229
+ assert_nothing_raised { configure { |c| conf = c } }
230
+ assert conf.is_a?(Twibot::Config)
231
+ end
232
+
233
+ should "add handler" do
234
+ Twibot::Macros.bot = Twibot::Bot.new Twibot::Config.default
235
+ bot.prompt = false
236
+
237
+ handler = add_handler(:message, ":command", :from => :cjno)
238
+ assert handler.is_a?(Twibot::Handler), handler.class
239
+ end
240
+
241
+ should "provide twitter macro" do
242
+ assert respond_to?(:twitter)
243
+ assert respond_to?(:client)
244
+ end
245
+
246
+ context "posting replies" do
247
+ should "work with string messages" do
248
+ text = "Hey there"
249
+ status = Twitter::Status.new(:id => 123,
250
+ :text => "Some text",
251
+ :user => Twitter::User.new(:screen_name => "cjno"))
252
+ client.expects(:status).with(:reply, "@cjno #{text}", 123).returns(true)
253
+
254
+ assert post_reply(status, text)
255
+ end
256
+
257
+ should "work with status object messages" do
258
+ reply = Twitter::Status.new :text => "Hey there"
259
+ status = Twitter::Status.new(:id => 123,
260
+ :text => "Some text",
261
+ :user => Twitter::User.new(:screen_name => "cjno"))
262
+ client.expects(:status).with(:reply, "@cjno Hey there", 123).returns(true)
263
+
264
+ assert post_reply(status, reply)
265
+ end
266
+ end
267
+ end
268
+
269
+ class TestBotHandlers < Test::Unit::TestCase
270
+
271
+ should "include handlers" do
272
+ bot = Twibot::Bot.new(Twibot::Config.new)
273
+
274
+ assert_not_nil bot.handlers
275
+ assert_not_nil bot.handlers[:message]
276
+ assert_not_nil bot.handlers[:reply]
277
+ assert_not_nil bot.handlers[:tweet]
278
+ end
279
+
280
+ should "add handler" do
281
+ bot = Twibot::Bot.new(Twibot::Config.new)
282
+ bot.add_handler :message, Twibot::Handler.new
283
+ assert_equal 1, bot.handlers[:message].length
284
+
285
+ bot.add_handler :message, Twibot::Handler.new
286
+ assert_equal 2, bot.handlers[:message].length
287
+
288
+ bot.add_handler :reply, Twibot::Handler.new
289
+ assert_equal 1, bot.handlers[:reply].length
290
+
291
+ bot.add_handler :reply, Twibot::Handler.new
292
+ assert_equal 2, bot.handlers[:reply].length
293
+
294
+ bot.add_handler :tweet, Twibot::Handler.new
295
+ assert_equal 1, bot.handlers[:tweet].length
296
+
297
+ bot.add_handler :tweet, Twibot::Handler.new
298
+ assert_equal 2, bot.handlers[:tweet].length
299
+ end
300
+ end
@@ -0,0 +1,89 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper')) unless defined?(Twibot)
2
+ require 'stringio'
3
+
4
+ class TestConfig < Test::Unit::TestCase
5
+ should "default configuration be a hash" do
6
+ assert_not_nil Twibot::Config::DEFAULT
7
+ assert Twibot::Config::DEFAULT.is_a?(Hash)
8
+ end
9
+
10
+ should "initialize with no options" do
11
+ assert_hashes_equal({}, Twibot::Config.new.settings)
12
+ end
13
+
14
+ should "return config from add" do
15
+ config = Twibot::Config.new
16
+ assert_equal config, config.add(Twibot::Config.new)
17
+ end
18
+
19
+ should "alias add to <<" do
20
+ config = Twibot::Config.new
21
+ assert config.respond_to?(:<<)
22
+ assert config << Twibot::Config.new
23
+ end
24
+
25
+ should "mirror method_missing as config getters" do
26
+ config = Twibot::Config.default << Twibot::Config.new
27
+ assert_equal Twibot::Config::DEFAULT[:min_interval], config.min_interval
28
+ assert_equal Twibot::Config::DEFAULT[:login], config.login
29
+ end
30
+
31
+ should "mirror missing methods as config setters" do
32
+ config = Twibot::Config.default << Twibot::Config.new
33
+ assert_equal Twibot::Config::DEFAULT[:min_interval], config.min_interval
34
+
35
+ val = config.min_interval
36
+ config.min_interval = val + 5
37
+ assert_not_equal Twibot::Config::DEFAULT[:min_interval], config.min_interval
38
+ assert_equal val + 5, config.min_interval
39
+ end
40
+
41
+ should "not override default hash" do
42
+ config = Twibot::Config.default
43
+ hash = Twibot::Config::DEFAULT
44
+
45
+ config.min_interval = 0
46
+ config.max_interval = 0
47
+
48
+ assert_hashes_not_equal Twibot::Config::DEFAULT, config.to_hash
49
+ assert_hashes_equal hash, Twibot::Config::DEFAULT
50
+ end
51
+
52
+ should "return merged configuration from to_hash" do
53
+ config = Twibot::Config.new
54
+ config.min_interval = 10
55
+ config.max_interval = 10
56
+
57
+ config2 = Twibot::Config.new({})
58
+ config2.min_interval = 1
59
+ config << config2
60
+ options = config.to_hash
61
+
62
+ assert_equal 10, options[:max_interval]
63
+ assert_equal 1, options[:min_interval]
64
+ end
65
+ end
66
+
67
+ class TestCliConfig < Test::Unit::TestCase
68
+ should "configure from options" do
69
+ config = Twibot::CliConfig.new %w{--min-interval 10 --max-interval 15}
70
+ assert_equal 10, config.min_interval
71
+ assert_equal 15, config.max_interval
72
+ end
73
+ end
74
+
75
+ class TestFileConfig < Test::Unit::TestCase
76
+ should "subclass config for file config" do
77
+ assert Twibot::FileConfig.new(StringIO.new).is_a?(Twibot::Config)
78
+ end
79
+
80
+ should "read settings from stream" do
81
+ config = Twibot::FileConfig.new(StringIO.new <<-YAML)
82
+ min_interval: 10
83
+ max_interval: 20
84
+ YAML
85
+
86
+ assert_equal 10, config.min_interval
87
+ assert_equal 20, config.max_interval
88
+ end
89
+ end