Class: Safubot::Bot
- Inherits:
-
Object
- Object
- Safubot::Bot
- Includes:
- Evented
- Defined in:
- lib/safubot/bot.rb,
lib/safubot/test_helper.rb
Overview
The main event-processing class. You are encouraged to inherit from this class when building your own bot, but delegation is also entirely feasible.
Instance Attribute Summary collapse
-
#opts ⇒ Object
readonly
Returns the value of attribute opts.
-
#subbots ⇒ Object
readonly
Returns the value of attribute subbots.
-
#twitter ⇒ Object
readonly
Returns the value of attribute twitter.
-
#xmpp ⇒ Object
readonly
Returns the value of attribute xmpp.
Instance Method Summary collapse
-
#answer(text, user = nil) ⇒ Object
A generic “respond immediately” interface.
-
#concurrently(req, &blk) ⇒ Object
Wraps Thread.new with error handling and response pushing for the given request.
-
#dispatch(resp) ⇒ Object
Performs appropriate dispatch operation for response type.
-
#dispatch_error(resp, e) ⇒ Object
Records an error in dispatch and emits a corresponding :dispatch_error event.
-
#enable_twitter(opts = {}) ⇒ Object
Initialises Twitter-related functionality.
-
#enable_xmpp(options = {}) ⇒ Object
Initialises XMPP-related functionality.
-
#initialize(options = {}) ⇒ Bot
constructor
A new instance of Bot.
-
#process ⇒ Object
Goes through each unprocessed Request and submits it for processing.
-
#process_request(req) ⇒ Object
Processes an individual request (synchronously).
-
#pull ⇒ Object
This pulls requests from passive non-streaming sources (currently, the Twitter AJAX API).
-
#push ⇒ Object
Dispatches all undispatched Responses.
-
#request_error(req, e) ⇒ Object
Records an error in processing and emits a corresponding :request_error event.
-
#respond(req, text) ⇒ Object
Adds a response to the queue and dispatches it.
- #run ⇒ Object
-
#stop ⇒ Object
Shuts down the streaming processes.
Methods included from Evented
#bind, #emit, #on, #once, #unbind
Constructor Details
#initialize(options = {}) ⇒ Bot
Returns a new instance of Bot.
260 261 262 263 264 265 266 267 268 |
# File 'lib/safubot/bot.rb', line 260 def initialize(={}) defaults = { :database => "safubot" } @opts = defaults.merge() MongoMapper.database = @opts[:database] MongoMapper.connection = Mongo::Connection.new('localhost', 27017, :pool_size => 5) MongoMapper::Document.plugin(MongoMapper::Plugins::IdentityMap) @handlers = {} @subbots = [] end |
Instance Attribute Details
#opts ⇒ Object (readonly)
Returns the value of attribute opts.
81 82 83 |
# File 'lib/safubot/bot.rb', line 81 def opts @opts end |
#subbots ⇒ Object (readonly)
Returns the value of attribute subbots.
81 82 83 |
# File 'lib/safubot/bot.rb', line 81 def subbots @subbots end |
#twitter ⇒ Object (readonly)
Returns the value of attribute twitter.
81 82 83 |
# File 'lib/safubot/bot.rb', line 81 def twitter @twitter end |
#xmpp ⇒ Object (readonly)
Returns the value of attribute xmpp.
81 82 83 |
# File 'lib/safubot/bot.rb', line 81 def xmpp @xmpp end |
Instance Method Details
#answer(text, user = nil) ⇒ Object
A generic “respond immediately” interface.
65 66 67 68 69 70 71 |
# File 'lib/safubot/test_helper.rb', line 65 def answer(text, user=nil) user ||= KnownUser.by_name('testing') query = Query.create(:user => user, :text => text) req = query.make_request process_request(req) req.responses.first.text end |
#concurrently(req, &blk) ⇒ Object
Wraps Thread.new with error handling and response pushing for the given request.
211 212 213 214 215 216 217 218 219 220 |
# File 'lib/safubot/bot.rb', line 211 def concurrently(req, &blk) Thread.new do begin blk.call rescue Exception => e request_error(req, e) end push end end |
#dispatch(resp) ⇒ Object
Performs appropriate dispatch operation for response type.
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
# File 'lib/safubot/bot.rb', line 138 def dispatch(resp) resp.reload if resp.dispatched Log.debug "Response '#{resp.text}' has already been dispatched, ignoring." return elsif resp.dispatching Log.debug "Response '#{resp.text}' is already in dispatch, ignoring." return elsif resp.problems.length > 10 Log.debug "Response '#{resp.text}' encountered more than ten dispatch errors, ignoring." return elsif !resp.problems.empty? && (Time.now - resp.last_problem.when) < 1.minute Log.debug "Response '#{resp.text}' encountered a dispatch error <1 minute ago, ignoring." return end begin source = resp.request.source resp.dispatching = true resp.save if Safubot::mode != :production Log.info "#{source.class} Response to #{source.username}: #{resp.text}" else if @twitter && [Twitter::Tweet, Twitter::DirectMessage].include?(source.class) @twitter.send(resp) elsif @xmpp && [XMPP::Message].include?(source.class) @xmpp.send(resp) else raise NotImplementedError, "Don't know how to send response to a #{source.class}!" end resp.dispatched = true resp.save end rescue Exception => e dispatch_error(resp, e) ensure resp.dispatching = false resp.save end end |
#dispatch_error(resp, e) ⇒ Object
Records an error in dispatch and emits a corresponding :dispatch_error event.
98 99 100 101 102 103 |
# File 'lib/safubot/bot.rb', line 98 def dispatch_error(resp, e) Log.error "Error dispatching #{resp.request.source.class} '#{resp.text}': #{error_report(e)}" resp.add_problem(e) resp.save emit(:dispatch_error, resp, e) end |
#enable_twitter(opts = {}) ⇒ Object
Initialises Twitter-related functionality.
240 241 242 243 244 245 246 247 |
# File 'lib/safubot/bot.rb', line 240 def enable_twitter(opts={}) @twitter = Twitter::Bot.new(opts) @twitter.on(:request) do |req| process_request(req) req.responses.where(:dispatched => false).map(&method(:dispatch)) end @subbots.push(@twitter) end |
#enable_xmpp(options = {}) ⇒ Object
Initialises XMPP-related functionality.
250 251 252 253 254 255 256 257 258 |
# File 'lib/safubot/bot.rb', line 250 def enable_xmpp(={}) defaults = { :jid => nil, :password => nil } @xmpp = XMPP::Bot.new(defaults.merge()) @xmpp.on(:request) do |req| process_request(req) req.responses.where(:dispatched => false).map(&method(:dispatch)) end @subbots.push(@xmpp) end |
#process ⇒ Object
Goes through each unprocessed Request and submits it for processing.
187 188 189 190 191 |
# File 'lib/safubot/bot.rb', line 187 def process Request.where(:processed => false).each do |req| concurrently(req) { process_request(req) } end end |
#process_request(req) ⇒ Object
Processes an individual request (synchronously).
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/safubot/bot.rb', line 108 def process_request(req) req.reload if req.processed Log.debug "Request '#{req.text}' has already been processed, ignoring." return elsif req.processing Log.debug "Request '#{req.text}' is currently in processing, ignoring." return end begin req.processing = true req.save emit(:request, req) rescue Exception => e request_error(req, e) else req.success = true ensure #if Safubot::mode == :production req.processing = false req.processed = true req.save #end end end |
#pull ⇒ Object
This pulls requests from passive non-streaming sources (currently, the Twitter AJAX API).
182 183 184 |
# File 'lib/safubot/bot.rb', line 182 def pull @twitter.pull if @twitter end |
#push ⇒ Object
Dispatches all undispatched Responses.
203 204 205 |
# File 'lib/safubot/bot.rb', line 203 def push Response.where(:dispatched => false).each(&method(:dispatch)) end |
#request_error(req, e) ⇒ Object
Records an error in processing and emits a corresponding :request_error event.
87 88 89 90 91 92 |
# File 'lib/safubot/bot.rb', line 87 def request_error(req, e) Log.error "Error processing #{req.source.class} '#{req.text}': #{error_report(e)}" req.add_problem(e) req.save emit(:request_error, req, e) end |
#respond(req, text) ⇒ Object
Adds a response to the queue and dispatches it.
197 198 199 200 |
# File 'lib/safubot/bot.rb', line 197 def respond(req, text) Log.info("#{req.user.name}: #{req.text}\nsafubot: #{text}") dispatch(Response.create(:request => req, :text => text)) end |
#run ⇒ Object
222 223 224 225 226 227 228 229 230 231 232 |
# File 'lib/safubot/bot.rb', line 222 def run pull; process; push @subbots[0..-2].each do |subbot| subbot.fork end # Run the final subbot in this process to avoid unnecessary forking # and thus memory consumption. @subbots[-1].run end |
#stop ⇒ Object
Shuts down the streaming processes.
235 236 237 |
# File 'lib/safubot/bot.rb', line 235 def stop @subbots.each { |subbot| subbot.stop } end |