bjeanes-twibot 0.1.6
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +24 -0
- data/Rakefile +30 -0
- data/Readme.rdoc +175 -0
- data/lib/hash.rb +8 -0
- data/lib/twibot/bot.rb +231 -0
- data/lib/twibot/config.rb +138 -0
- data/lib/twibot/handlers.rb +111 -0
- data/lib/twibot/macros.rb +66 -0
- data/lib/twibot/tweets.rb +4 -0
- data/lib/twibot.rb +87 -0
- data/test/test_bot.rb +185 -0
- data/test/test_config.rb +89 -0
- data/test/test_handler.rb +191 -0
- data/test/test_hash.rb +34 -0
- data/test/test_helper.rb +44 -0
- data/test/test_twibot.rb +1 -0
- data/twibot.gemspec +38 -0
- metadata +95 -0
data/History.txt
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
== 0.1.4 / 2009-03-24
|
2
|
+
|
3
|
+
* Removed some warnings
|
4
|
+
* Added error handling to avoid Twibot crashing when Twitter is down (Ben Vandgrift)
|
5
|
+
* Fixed bug: receiving tweets from named users crashed Twibot (Jens Ohlig)
|
6
|
+
|
7
|
+
== 0.1.3 / 2009-03-19
|
8
|
+
|
9
|
+
* Ruby 1.9 support
|
10
|
+
|
11
|
+
== 0.1.2 / 2009-03-18
|
12
|
+
|
13
|
+
* Removed some warnings
|
14
|
+
* Applied patch from Dan Van Derveer fixing a few minor bugs related to the
|
15
|
+
options hash sent to Twitter4R
|
16
|
+
|
17
|
+
== 0.1.1 / 2009-03-15
|
18
|
+
|
19
|
+
* Fixed dependency
|
20
|
+
|
21
|
+
== 0.1.0 / 2009-03-15
|
22
|
+
|
23
|
+
* 1 major enhancement
|
24
|
+
* Birthday!
|
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# Look in the tasks/setup.rb file for the various options that can be
|
2
|
+
# configured in this Rakefile. The .rake files in the tasks directory
|
3
|
+
# are where the options are used.
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'bones'
|
7
|
+
Bones.setup
|
8
|
+
rescue LoadError
|
9
|
+
begin
|
10
|
+
load 'tasks/setup.rb'
|
11
|
+
rescue LoadError
|
12
|
+
raise RuntimeError, '### please install the "bones" gem ###'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
ensure_in_path 'lib'
|
17
|
+
require 'twibot'
|
18
|
+
|
19
|
+
task :default => 'test:run'
|
20
|
+
|
21
|
+
PROJ.name = 'twibot'
|
22
|
+
PROJ.authors = 'Christian Johansen'
|
23
|
+
PROJ.email = 'christian@cjohansen.no'
|
24
|
+
PROJ.url = 'http://github.com/bjeanes/twibot/'
|
25
|
+
PROJ.version = Twibot::VERSION
|
26
|
+
PROJ.rubyforge.name = 'twibot'
|
27
|
+
PROJ.readme_file = 'Readme.rdoc'
|
28
|
+
PROJ.rdoc.remote_dir = 'twibot'
|
29
|
+
|
30
|
+
depend_on "mbbx6spp-twitter4r", "0.3.1"
|
data/Readme.rdoc
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
= Twibot
|
2
|
+
Official URL: http://github.com/cjohansen/twibot/tree/master
|
3
|
+
Christian Johansen (http://www.cjohansen.no)
|
4
|
+
Twitter: @cjno
|
5
|
+
|
6
|
+
== Description
|
7
|
+
|
8
|
+
Twibot (pronounced like "Abbot"), is a Ruby microframework for creating Twitter
|
9
|
+
bots, heavily inspired by Sinatra.
|
10
|
+
|
11
|
+
== Usage
|
12
|
+
|
13
|
+
=== Simple example
|
14
|
+
|
15
|
+
require 'twibot'
|
16
|
+
|
17
|
+
# Receive messages, and tweet them publicly
|
18
|
+
#
|
19
|
+
message do |message, params|
|
20
|
+
post_tweet message
|
21
|
+
end
|
22
|
+
|
23
|
+
# Respond to @replies if they come from the right crowd
|
24
|
+
#
|
25
|
+
reply :from => [:cjno, :irbno] do |message, params|
|
26
|
+
post_tweet "@#{message.sender.screen_name} I agree"
|
27
|
+
end
|
28
|
+
|
29
|
+
# Listen in and log tweets
|
30
|
+
#
|
31
|
+
tweet do |message, params|
|
32
|
+
MyApp.log_tweet(message)
|
33
|
+
end
|
34
|
+
|
35
|
+
=== Running the bot
|
36
|
+
|
37
|
+
To run the bot, simply do:
|
38
|
+
|
39
|
+
ruby bot.rb
|
40
|
+
|
41
|
+
=== Configuration
|
42
|
+
|
43
|
+
Twibot looks for a configuration file in ./config/bot.yml. It should contain
|
44
|
+
atleast:
|
45
|
+
|
46
|
+
login: twitter_login
|
47
|
+
password: twitter_password
|
48
|
+
|
49
|
+
You can also pass configuration as command line arguments:
|
50
|
+
|
51
|
+
ruby bot.rb --login myaccount
|
52
|
+
|
53
|
+
...or configure with Ruby:
|
54
|
+
|
55
|
+
configure do |conf|
|
56
|
+
conf.login = "my_account"
|
57
|
+
do
|
58
|
+
|
59
|
+
If you don't specify login and/or password in any of these ways, Twibot will
|
60
|
+
prompt you for those.
|
61
|
+
|
62
|
+
If you want to change how Twibot is configured, you can setup the bot instance
|
63
|
+
manually and give it only the configuration options you want:
|
64
|
+
|
65
|
+
# Create bot only with default configuration
|
66
|
+
require 'twibot'
|
67
|
+
bot = Twibot::Bot.new(Twibot::Config.default)
|
68
|
+
|
69
|
+
# Application here...
|
70
|
+
|
71
|
+
If you want command line arguments you can do:
|
72
|
+
|
73
|
+
require 'twibot'
|
74
|
+
bot = Twibot::Bot.new(Twibot::Config.default << Twibot::CliConfig.new)
|
75
|
+
|
76
|
+
=== "Routes"
|
77
|
+
|
78
|
+
Like Sinatra, and other web app frameworks, Twibot supports "routes": patterns
|
79
|
+
to match incoming tweets and messages:
|
80
|
+
|
81
|
+
require 'twibot'
|
82
|
+
|
83
|
+
tweet "time :country :city" do |message,params|
|
84
|
+
time = MyTimeService.lookup(params[:country], params[:city])
|
85
|
+
client.message :post, "Time is #{time} in #{params[:city]}, #{params[:country]}"
|
86
|
+
end
|
87
|
+
|
88
|
+
You can have several "tweet" blocks (or "message" or "reply"). The first one to
|
89
|
+
match an incoming tweet/message will handle it.
|
90
|
+
|
91
|
+
As of the upcoming 0.1.5/0.2.0, Twibot also supports regular expressions as routes:
|
92
|
+
|
93
|
+
require 'twibot'
|
94
|
+
|
95
|
+
tweet /^time ([^\s]*) ([^\s]*)/ do |message, params|
|
96
|
+
# params is an array of matches when using regexp routes
|
97
|
+
time = MyTimeService.lookup(params[0], params[1])
|
98
|
+
client.message :post, "Time is #{time} in #{params[:city]}, #{params[:country]}"
|
99
|
+
end
|
100
|
+
|
101
|
+
=== Working with the Twitter API
|
102
|
+
|
103
|
+
The DSL gives you access to your Twitter client instance through "client" (or "twitter"):
|
104
|
+
|
105
|
+
message do
|
106
|
+
twitter.status :post, "Hello world" # Also: client.status :post, "Hello world"
|
107
|
+
end
|
108
|
+
|
109
|
+
== Requirements
|
110
|
+
|
111
|
+
Twitter4r. You'll need atleast 0.3.1, which is currently only available from GitHub.
|
112
|
+
Versions of Twitter4r prior to 0.3.1 does not allow for the since_id parameter to be
|
113
|
+
appended to URLs to the REST API. Twibot needs these to only fetch fresh messages
|
114
|
+
and tweets.
|
115
|
+
|
116
|
+
== Installation
|
117
|
+
|
118
|
+
gem install twibot
|
119
|
+
|
120
|
+
== Is it Ruby 1.9?
|
121
|
+
|
122
|
+
As of Twibot 0.1.3, yes it is! All tests pass, please give feedback from real world
|
123
|
+
usage if you have trouble.
|
124
|
+
|
125
|
+
== Polling
|
126
|
+
|
127
|
+
Twitter pulled the plug on it's xmpp service last year. This means that Twibot backed
|
128
|
+
bots needs to poll the Twitter service to keep up. Twitter has a request limit on 70
|
129
|
+
reqs/hour, so you should configure your bot not to make more than that, else it will
|
130
|
+
fail. You can ask for your bot account to be put on the whitelist which allows you to
|
131
|
+
make 20.000 reqs/hour, and shouldn't be a problem so long as your intentions are good
|
132
|
+
(I think).
|
133
|
+
|
134
|
+
Twibot polls like this:
|
135
|
+
* Poll messages if any message handlers exist
|
136
|
+
* Poll tweets if any tweet or reply handlers exist
|
137
|
+
* Sleep for +interval+ seconds
|
138
|
+
* Go over again
|
139
|
+
|
140
|
+
As long as Twibot finds any messages and/or tweets, the interval stays the same
|
141
|
+
(min_interval configuration switch). If nothing was found however, the interval to
|
142
|
+
sleep is increased by interval_step configuration option. This happens until it
|
143
|
+
reaches max_interval, where it will stay until Twibot finds anything.
|
144
|
+
|
145
|
+
== Contributors
|
146
|
+
|
147
|
+
* Dan Van Derveer (bug fixes) - http://dan.van.derveer.com/
|
148
|
+
* Ben Vandgrift (Twitter downtime error handling) - http://neovore.com/
|
149
|
+
* Jens Ohlig (warnings)
|
150
|
+
* Wilco van Duinkerken (bug fixes) - http://www.sparkboxx.com/
|
151
|
+
|
152
|
+
== License
|
153
|
+
|
154
|
+
(The MIT License)
|
155
|
+
|
156
|
+
Copyright (c) 2009 Christian Johansen
|
157
|
+
|
158
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
159
|
+
a copy of this software and associated documentation files (the
|
160
|
+
'Software'), to deal in the Software without restriction, including
|
161
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
162
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
163
|
+
permit persons to whom the Software is furnished to do so, subject to
|
164
|
+
the following conditions:
|
165
|
+
|
166
|
+
The above copyright notice and this permission notice shall be
|
167
|
+
included in all copies or substantial portions of the Software.
|
168
|
+
|
169
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
170
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
171
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
172
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
173
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
174
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
175
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/lib/hash.rb
ADDED
data/lib/twibot/bot.rb
ADDED
@@ -0,0 +1,231 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require File.join(File.expand_path(File.dirname(__FILE__)), 'macros')
|
3
|
+
require File.join(File.expand_path(File.dirname(__FILE__)), 'handlers')
|
4
|
+
|
5
|
+
module Twibot
|
6
|
+
#
|
7
|
+
# Main bot "controller" class
|
8
|
+
#
|
9
|
+
class Bot
|
10
|
+
include Twibot::Handlers
|
11
|
+
attr_reader :twitter
|
12
|
+
attr_writer :prompt
|
13
|
+
|
14
|
+
def initialize(options = nil, prompt = false)
|
15
|
+
@prompt = prompt
|
16
|
+
@conf = nil
|
17
|
+
@config = options || Twibot::Config.default << Twibot::FileConfig.new << Twibot::CliConfig.new
|
18
|
+
@log = nil
|
19
|
+
@abort = false
|
20
|
+
rescue Exception => krash
|
21
|
+
raise SystemExit.new(krash.message)
|
22
|
+
end
|
23
|
+
|
24
|
+
def prompt?
|
25
|
+
@prompt
|
26
|
+
end
|
27
|
+
|
28
|
+
def processed
|
29
|
+
@processed ||= {
|
30
|
+
:message => nil,
|
31
|
+
:reply => nil,
|
32
|
+
:tweet => nil
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def twitter
|
37
|
+
@twitter ||= Twitter::Client.new :login => config[:login], :password => config[:password]
|
38
|
+
end
|
39
|
+
|
40
|
+
#
|
41
|
+
# Run application
|
42
|
+
#
|
43
|
+
def run!
|
44
|
+
puts "Twibot #{Twibot::VERSION} imposing as @#{login}"
|
45
|
+
|
46
|
+
trap(:INT) do
|
47
|
+
puts "\nAnd it's a wrap. See ya soon!"
|
48
|
+
exit
|
49
|
+
end
|
50
|
+
|
51
|
+
# Make sure we don't process messages and tweets received prior to bot launch
|
52
|
+
messages = twitter.messages(:received, { :count => 1 })
|
53
|
+
processed[:message] = messages.first.id if messages.length > 0
|
54
|
+
|
55
|
+
handle_tweets = !@handlers.nil? && @handlers[:tweet].length + @handlers[:reply].length > 0
|
56
|
+
tweets = []
|
57
|
+
|
58
|
+
begin
|
59
|
+
tweets = handle_tweets ? twitter.timeline_for(config[:timeline_for], { :count => 1 }) : []
|
60
|
+
rescue Twitter::RESTError => e
|
61
|
+
log.error("Failed to connect to Twitter. It's likely down for a bit:")
|
62
|
+
log.error(e.to_s)
|
63
|
+
end
|
64
|
+
|
65
|
+
processed[:tweet] = tweets.first.id if tweets.length > 0
|
66
|
+
processed[:reply] = tweets.first.id if tweets.length > 0
|
67
|
+
|
68
|
+
poll
|
69
|
+
end
|
70
|
+
|
71
|
+
#
|
72
|
+
# Poll Twitter API in a loop and pass on messages and tweets when they appear
|
73
|
+
#
|
74
|
+
def poll
|
75
|
+
max = max_interval
|
76
|
+
step = interval_step
|
77
|
+
interval = min_interval
|
78
|
+
|
79
|
+
while !@abort do
|
80
|
+
message_count = 0
|
81
|
+
message_count += receive_messages || 0
|
82
|
+
message_count += receive_replies || 0
|
83
|
+
message_count += receive_tweets || 0
|
84
|
+
|
85
|
+
interval = message_count > 0 ? min_interval : [interval + step, max].min
|
86
|
+
|
87
|
+
log.debug "Sleeping for #{interval}s"
|
88
|
+
sleep interval
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
#
|
93
|
+
# Receive direct messages
|
94
|
+
#
|
95
|
+
def receive_messages
|
96
|
+
type = :message
|
97
|
+
return false unless handlers[type].length > 0
|
98
|
+
options = {}
|
99
|
+
options[:since_id] = processed[type] if processed[type]
|
100
|
+
begin
|
101
|
+
dispatch_messages(type, twitter.messages(:received, options), %w{message messages})
|
102
|
+
rescue Twitter::RESTError => e
|
103
|
+
log.error("Failed to connect to Twitter. It's likely down for a bit:")
|
104
|
+
log.error(e.to_s)
|
105
|
+
0
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
#
|
110
|
+
# Receive tweets
|
111
|
+
#
|
112
|
+
def receive_tweets
|
113
|
+
type = :tweet
|
114
|
+
return false unless handlers[type].length > 0
|
115
|
+
options = {}
|
116
|
+
options[:since_id] = processed[type] if processed[type]
|
117
|
+
begin
|
118
|
+
dispatch_messages(type,
|
119
|
+
twitter.timeline_for(config[:include_friends] ? :friends : :me,
|
120
|
+
options), %w{tweet tweets})
|
121
|
+
rescue Twitter::RESTError => e
|
122
|
+
log.error("Failed to connect to Twitter. It's likely down for a bit:")
|
123
|
+
log.error(e.to_s)
|
124
|
+
0
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
#
|
129
|
+
# Receive tweets that start with @<login>
|
130
|
+
#
|
131
|
+
def receive_replies
|
132
|
+
type = :reply
|
133
|
+
return false unless handlers[type].length > 0
|
134
|
+
options = {}
|
135
|
+
options[:since_id] = processed[type] if processed[type]
|
136
|
+
begin
|
137
|
+
dispatch_messages(type, twitter.status(:replies, options), %w{reply replies})
|
138
|
+
rescue Twitter::RESTError => e
|
139
|
+
log.error("Failed to connect to Twitter. It's likely down for a bit:")
|
140
|
+
log.error(e.to_s)
|
141
|
+
0
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
#
|
147
|
+
# Dispatch a collection of messages
|
148
|
+
#
|
149
|
+
def dispatch_messages(type, messages, labels)
|
150
|
+
messages.each { |message| dispatch(type, message) }
|
151
|
+
# Avoid picking up messages over again
|
152
|
+
processed[type] = messages.first.id if messages.length > 0
|
153
|
+
|
154
|
+
num = messages.length
|
155
|
+
log.info "Received #{num} #{num == 1 ? labels[0] : labels[1]}"
|
156
|
+
num
|
157
|
+
end
|
158
|
+
|
159
|
+
#
|
160
|
+
# Return logger instance
|
161
|
+
#
|
162
|
+
def log
|
163
|
+
return @log if @log
|
164
|
+
os = config[:log_file] ? File.open(config[:log_file], "a") : $stdout
|
165
|
+
@log = Logger.new(os)
|
166
|
+
@log.level = Logger.const_get(config[:log_level] ? config[:log_level].upcase : "INFO")
|
167
|
+
@log
|
168
|
+
end
|
169
|
+
|
170
|
+
#
|
171
|
+
# Configure bot
|
172
|
+
#
|
173
|
+
def configure
|
174
|
+
yield @config
|
175
|
+
@conf = nil
|
176
|
+
@twitter = nil
|
177
|
+
end
|
178
|
+
|
179
|
+
private
|
180
|
+
#
|
181
|
+
# Map configuration settings
|
182
|
+
#
|
183
|
+
def method_missing(name, *args, &block)
|
184
|
+
return super unless config.key?(name)
|
185
|
+
|
186
|
+
self.class.send(:define_method, name) { config[name] }
|
187
|
+
config[name]
|
188
|
+
end
|
189
|
+
|
190
|
+
#
|
191
|
+
# Return configuration
|
192
|
+
#
|
193
|
+
def config
|
194
|
+
return @conf if @conf
|
195
|
+
@conf = @config.to_hash
|
196
|
+
|
197
|
+
if prompt? && (!@conf[:login] || !@conf[:password])
|
198
|
+
# No need to rescue LoadError - if the gem is missing then config will
|
199
|
+
# be incomplete, something which will be detected elsewhere
|
200
|
+
begin
|
201
|
+
require 'highline'
|
202
|
+
hl = HighLine.new
|
203
|
+
|
204
|
+
@config.login = hl.ask("Twitter login: ") unless @conf[:login]
|
205
|
+
@config.password = hl.ask("Twitter password: ") { |q| q.echo = '*' } unless @conf[:password]
|
206
|
+
@conf = @config.to_hash
|
207
|
+
rescue LoadError
|
208
|
+
raise SystemExit.new( <<-HELP
|
209
|
+
Unable to continue without login and password. Do one of the following:
|
210
|
+
1) Install the HighLine gem (gem install highline) to be prompted for credentials
|
211
|
+
2) Create a config/bot.yml with login: and password:
|
212
|
+
3) Put a configure { |conf| conf.login = "..." } block in your bot application
|
213
|
+
4) Run bot with --login and --password options
|
214
|
+
HELP
|
215
|
+
)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
@conf
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
# Expose DSL
|
225
|
+
include Twibot::Macros
|
226
|
+
|
227
|
+
# Run bot if macros has been used
|
228
|
+
at_exit do
|
229
|
+
raise $! if $!
|
230
|
+
@@bot.run! if run?
|
231
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module Twibot
|
4
|
+
#
|
5
|
+
# Twibot configuration. Use either Twibot::CliConfig.new or
|
6
|
+
# TwibotFileConfig.new setup a new bot from either command line or file
|
7
|
+
# (respectively). Configurations can be chained so they override each other:
|
8
|
+
#
|
9
|
+
# config = Twibot::FileConfig.new
|
10
|
+
# config << Twibot::CliConfig.new
|
11
|
+
# config.to_hash
|
12
|
+
#
|
13
|
+
# The preceding example will create a configuration which is based on a
|
14
|
+
# configuration file but have certain values overridden from the command line.
|
15
|
+
# This can be used for instance to store everything but the Twitter account
|
16
|
+
# password in your configuration file. Then you can just provide the password
|
17
|
+
# when running the bot.
|
18
|
+
#
|
19
|
+
class Config
|
20
|
+
attr_reader :settings
|
21
|
+
|
22
|
+
DEFAULT = {
|
23
|
+
:min_interval => 30,
|
24
|
+
:max_interval => 300,
|
25
|
+
:interval_step => 10,
|
26
|
+
:log_level => "info",
|
27
|
+
:log_file => nil,
|
28
|
+
:login => nil,
|
29
|
+
:password => nil,
|
30
|
+
:prompt => false,
|
31
|
+
:daemonize => false,
|
32
|
+
:include_friends => false,
|
33
|
+
:timeline_for => :public
|
34
|
+
}
|
35
|
+
|
36
|
+
def initialize(settings = {})
|
37
|
+
@configs = []
|
38
|
+
@settings = settings
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# Add a configuration object to override given settings
|
43
|
+
#
|
44
|
+
def add(config)
|
45
|
+
@configs << config
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
alias_method :<<, :add
|
50
|
+
|
51
|
+
#
|
52
|
+
# Makes it possible to access configuration settings as attributes
|
53
|
+
#
|
54
|
+
def method_missing(name, *args, &block)
|
55
|
+
regex = /=$/
|
56
|
+
attr_name = name.to_s.sub(regex, '').to_sym
|
57
|
+
return super if name == attr_name && !@settings.key?(attr_name)
|
58
|
+
|
59
|
+
if name != attr_name
|
60
|
+
@settings[attr_name] = args.first
|
61
|
+
end
|
62
|
+
|
63
|
+
@settings[attr_name]
|
64
|
+
end
|
65
|
+
|
66
|
+
#
|
67
|
+
# Merges configurations and returns a hash with all options
|
68
|
+
#
|
69
|
+
def to_hash
|
70
|
+
hash = {}.merge(@settings)
|
71
|
+
@configs.each { |conf| hash.merge!(conf.to_hash) }
|
72
|
+
hash
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.default
|
76
|
+
Config.new({}.merge(DEFAULT))
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
#
|
81
|
+
# Configuration from command line
|
82
|
+
#
|
83
|
+
class CliConfig < Config
|
84
|
+
|
85
|
+
def initialize(args = $*)
|
86
|
+
super()
|
87
|
+
|
88
|
+
@parser = OptionParser.new do |opts|
|
89
|
+
opts.banner += "Usage: #{File.basename(Twibot.app_file)} [options]"
|
90
|
+
|
91
|
+
opts.on("-m", "--min-interval SECS", Integer, "Minimum poll interval in seconds") { |i| @settings[:min_interval] = i }
|
92
|
+
opts.on("-x", "--max-interval SECS", Integer, "Maximum poll interval in seconds") { |i| @settings[:max_interval] = i }
|
93
|
+
opts.on("-s", "--interval-step SECS", Integer, "Poll interval step in seconds") { |i| @settings[:interval_step] = i }
|
94
|
+
opts.on("-f", "--log-file FILE", "Log file") { |f| @settings[:log_file] = f }
|
95
|
+
opts.on("-l", "--log-level LEVEL", "Log level (err, warn, info, debug), default id info") { |l| @settings[:log_level] = l }
|
96
|
+
opts.on("-u", "--login LOGIN", "Twitter login") { |l| @settings[:login] = l }
|
97
|
+
opts.on("-p", "--password PASSWORD", "Twitter password") { |p| @settings[:password] = p }
|
98
|
+
opts.on("-h", "--help", "Show this message") { puts opts; exit }
|
99
|
+
|
100
|
+
begin
|
101
|
+
require 'daemons'
|
102
|
+
opts.on("-d", "--daemonize", "Run as background process (Not implemented)") { |t| @settings[:daemonize] = true }
|
103
|
+
rescue LoadError
|
104
|
+
end
|
105
|
+
|
106
|
+
end.parse!(args)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
#
|
111
|
+
# Configuration from files
|
112
|
+
#
|
113
|
+
class FileConfig < Config
|
114
|
+
|
115
|
+
#
|
116
|
+
# Accepts a stream or a file to read configuration from
|
117
|
+
# Default is to read configuration from ./config/bot.yml
|
118
|
+
#
|
119
|
+
# If a stream is passed it is not closed from within the method
|
120
|
+
#
|
121
|
+
def initialize(fos = File.expand_path("config/bot.yml"))
|
122
|
+
stream = fos.is_a?(String) ? File.open(fos, "r") : fos
|
123
|
+
|
124
|
+
begin
|
125
|
+
config = YAML.load(stream.read)
|
126
|
+
config.symbolize_keys! if config
|
127
|
+
rescue Exception => err
|
128
|
+
puts err.message
|
129
|
+
puts "Unable to load configuration, aborting"
|
130
|
+
exit
|
131
|
+
ensure
|
132
|
+
stream.close if fos.is_a?(String)
|
133
|
+
end
|
134
|
+
|
135
|
+
super config.is_a?(Hash) ? config : {}
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|