Class: Butler::IRC::Client
- Inherits:
-
Object
- Object
- Butler::IRC::Client
- Defined in:
- lib/butler/irc/client.rb
Overview
Description
Wraps Butler::IRC::Socket, providing methods to be aware of the environment. It parses messages, keeps track of users and channels, is aware of events, responds to pings, provides simple methods for requests (e.g. whois, banlist, …) and offers dispatchers for messages and scheduled events. Butler::IRC::Client uses a separated thread for reading and dispatching.
Synopsis
irc_client = Butler::IRC::Client.new('irc.server.com', :port => 6667, :server_charset => 'utf-8')
irc_client.subscribe(:QUIT) { |listener, message| “#messagemessage.frommessage.from.nick has left us…” }
puts "Whois 'nickname':", irc_client.whois("nickname")
puts *irc_client.banlist("#channel")
irc_client.event_loop { ||
case
when /#{irc_client.myself.nick}[,:]/
.answer("#{.from.nick}, you spoke to me?")
when :JOIN
.from.notice("Welcome to #{.channel}!")
end
puts "received: #{}"
}
puts "If this point is reached, client has ended"
Direct Known Subclasses
Defined Under Namespace
Modules: Filter Classes: Listener, Terminate
Constant Summary collapse
- DefaultTimeout =
The timeout defaults
{ :login => 150, }
- DefaultOptions =
Defaults for the opts argument in Butler::Bot.new
{ :client_charset => 'utf-8', :server_charset => 'utf-8', :channel_charset => {}, :timeout => {}, }
Instance Attribute Summary collapse
-
#channel_charset ⇒ Object
readonly
The charsets of individual channel, only set if they differ from server_charset.
-
#channels ⇒ Object
readonly
Returns the value of attribute channels.
-
#client_charset ⇒ Object
The charset the client uses.
-
#irc ⇒ Object
readonly
The Butler::IRC::Socket, most send commands have to be used on this.
-
#myself ⇒ Object
readonly
The user representing the bot.
-
#server_charset ⇒ Object
The charset the server needs messages sent to him and sends message to this client in.
-
#users ⇒ Object
readonly
Returns the value of attribute users.
Instance Method Summary collapse
- #event_loop(priority = -1)) ⇒ Object
-
#filter(symbol, priority = 0, queue = Queue.new) ⇒ Object
listens for all Messages with symbol (optionally passing a test given as block) and pushes them onto the Queue returns the Queue, extended with Filter.
-
#initialize(server, options = {}, &on_disconnect) ⇒ Client
constructor
A new instance of Client.
-
#inspect ⇒ Object
:nodoc:.
-
#join(*args) ⇒ Object
Same as IRC::Socket#join, but will do a who on every joined channel.
-
#login(nick, user, real) ⇒ Object
login under nick, user, real.
- #on_disconnect(reason) ⇒ Object
-
#process(message) ⇒ Object
process a Butler::IRC::Message, normally fed from thread_read.
-
#quit(reason = nil) ⇒ Object
Sends quit message to server, terminates connection.
-
#subscribe(symbol = nil, priority = 0, id = nil, *args, &callback) ⇒ Object
callback is called whenever a message with Message#symbol == symbol (or on every message if symbol is nil) priority may be any numeric, higher priority is dispatched to first, lower priority later returns an Butler::IRC::Client::Listener.
-
#terminate(stop_reading = true) ⇒ Object
terminate all processing and reading, see Client#quit.
- #terminate_event_loop ⇒ Object
-
#thread_read ⇒ Object
this thread is responsible for reading the servers messages and dispatching them to responders normally this thread is alive as long as the client is connected to the server.
-
#unsubscribe(id) ⇒ Object
unsubscribe a listener by id.
-
#wait_for(symbol, timeout = nil, &test) ⇒ Object
blocks current thread until a Message with symbol (optionally passing a test given as block) is received, returns the message received that matches.
-
#whois(user) ⇒ Object
Do a whois on nick Returns an Butler::IRC::Whois-Struct.
Constructor Details
#initialize(server, options = {}, &on_disconnect) ⇒ Client
Returns a new instance of Client.
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/butler/irc/client.rb', line 102 def initialize(server, ={}, &on_disconnect) = DefaultOptions.merge() @users = Users.new(self) @channels = Channels.new(self) @users.channels = @channels @channels.users = @users @parser = Parser.new(self, @users, @channels) @client_charset = .delete(:client_charset) @server_charset = .delete(:server_charset) # proc needed because @server_charset might change @channel_charset = Hash.new { |h,k| @server_charset }.merge(.delete(:channel_charset)) @timeout = DefaultTimeout.merge(.delete(:timeout)) @irc = Socket.new(server, ) # the socket, all methods to the socket are wrapped @listen = Hash.new { |h,k| h[k] = [] } @listener = {} @event_loop = Queue.new @dispatch_lock = Mutex.new @thread_read = nil @myself = @users.myself subscribe(:PING, 100) { |listener, | @irc.pong(.pong) } end |
Instance Attribute Details
#channel_charset ⇒ Object (readonly)
The charsets of individual channel, only set if they differ from server_charset
91 92 93 |
# File 'lib/butler/irc/client.rb', line 91 def channel_charset @channel_charset end |
#channels ⇒ Object (readonly)
Returns the value of attribute channels.
93 94 95 |
# File 'lib/butler/irc/client.rb', line 93 def channels @channels end |
#client_charset ⇒ Object
The charset the client uses
83 84 85 |
# File 'lib/butler/irc/client.rb', line 83 def client_charset @client_charset end |
#irc ⇒ Object (readonly)
The Butler::IRC::Socket, most send commands have to be used on this
97 98 99 |
# File 'lib/butler/irc/client.rb', line 97 def irc @irc end |
#myself ⇒ Object (readonly)
The user representing the bot
100 101 102 |
# File 'lib/butler/irc/client.rb', line 100 def myself @myself end |
#server_charset ⇒ Object
The charset the server needs messages sent to him and sends message to this client in.
87 88 89 |
# File 'lib/butler/irc/client.rb', line 87 def server_charset @server_charset end |
#users ⇒ Object (readonly)
Returns the value of attribute users.
94 95 96 |
# File 'lib/butler/irc/client.rb', line 94 def users @users end |
Instance Method Details
#event_loop(priority = -1)) ⇒ Object
311 312 313 314 315 316 317 318 319 320 |
# File 'lib/butler/irc/client.rb', line 311 def event_loop(priority=-1) if block_given? filter(nil, priority, @event_loop) while = @event_loop.shift; yield(); end else sleep end ensure @event_loop.unsubscribe if block_given? end |
#filter(symbol, priority = 0, queue = Queue.new) ⇒ Object
listens for all Messages with symbol (optionally passing a test given as block) and pushes them onto the Queue returns the Queue, extended with Filter. You are responsible to unsubscribe it (call Queue#unsubscribe on it)
184 185 186 187 188 189 190 191 192 193 194 |
# File 'lib/butler/irc/client.rb', line 184 def filter(symbol, priority=0, queue=Queue.new) raise ArgumentError, "Invalid Queue #{queue}:#{queue.class}" unless queue.respond_to?(:push) listener = if block_given? then subscribe(symbol, priority) { |l, | queue.push() if yield() } else subscribe(symbol, priority) { |l, | queue.push() } end queue.extend Filter queue.listener = listener queue end |
#inspect ⇒ Object
:nodoc:
342 343 344 345 346 347 348 |
# File 'lib/butler/irc/client.rb', line 342 def inspect # :nodoc: "#<%s:0x%08x irc=%s>" % [ self.class, object_id << 1, @irc.inspect ] end |
#join(*args) ⇒ Object
Same as IRC::Socket#join, but will do a who on every joined channel
222 223 224 225 226 |
# File 'lib/butler/irc/client.rb', line 222 def join(*args) @irc.join(*args).each { |channel| @irc.who(channel) } end |
#login(nick, user, real) ⇒ Object
login under nick, user, real
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 |
# File 'lib/butler/irc/client.rb', line 197 def login(nick, user, real) queue, nick_change = nil, nil number = 0 timeout(@timeout[:login]) { @irc.connect @users.create_myself(nick, user, real) @myself = @users.myself queue = filter(:RPL_WELCOME) filter(:ERR_NOMOTD, 0, queue) nick_change = subscribe(:ERR_NICKNAMEINUSE) { change = "[#{number+=1}]#{nick}" @irc.nick(change) @myself.nick = change } @thread_read = Thread.new(&method(:thread_read)) @irc.login(nick, user, real) queue.shift } true ensure queue.unsubscribe if queue nick_change.unsubscribe if nick_change end |
#on_disconnect(reason) ⇒ Object
308 309 |
# File 'lib/butler/irc/client.rb', line 308 def on_disconnect(reason) end |
#process(message) ⇒ Object
process a Butler::IRC::Message, normally fed from thread_read.
327 328 329 330 331 332 333 334 335 336 337 338 339 340 |
# File 'lib/butler/irc/client.rb', line 327 def process() = @parser.() .transcode!(@channel_charset[.channel], @client_charset) @dispatch_lock.synchronize { @listen[nil].each { |listener| listener.callback.call(listener, ) } if @listen.has_key?(.symbol) @listen[.symbol].each { |listener| listener.callback.call(listener, , *listener.args) } end } rescue Terminate, Errno::EPIPE => error raise error # on these errors we got to get out of the loop rescue Exception => error exception(error) # those errors are logged, reading goes on end |
#quit(reason = nil) ⇒ Object
Sends quit message to server, terminates connection
276 277 278 279 280 |
# File 'lib/butler/irc/client.rb', line 276 def quit(reason=nil) @irc.quit(reason) terminate @irc.close end |
#subscribe(symbol = nil, priority = 0, id = nil, *args, &callback) ⇒ Object
callback is called whenever a message with Message#symbol == symbol (or on every message if symbol is nil) priority may be any numeric, higher priority is dispatched to first, lower priority later returns an Butler::IRC::Client::Listener
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/butler/irc/client.rb', line 134 def subscribe(symbol=nil, priority=0, id=nil, *args, &callback) id ||= callback raise "#{id} already subscribed" if @listener.has_key?(id) listener = Listener.new(priority, callback, args) { |item| @dispatch_lock.synchronize { @listener.delete(id) @listen[symbol].delete(item) @listen.delete(symbol) if @listen[symbol].empty? } } @dispatch_lock.synchronize { @listener[id] = listener @listen[symbol] << listener @listen[symbol].sort_by { |l| -l.priority } } listener end |
#terminate(stop_reading = true) ⇒ Object
terminate all processing and reading, see Client#quit
284 285 286 287 |
# File 'lib/butler/irc/client.rb', line 284 def terminate(stop_reading=true) @thread_read.raise Terminate if stop_reading and @thread_read and @thread_read.alive? terminate_event_loop end |
#terminate_event_loop ⇒ Object
322 323 324 |
# File 'lib/butler/irc/client.rb', line 322 def terminate_event_loop @event_loop.push(nil) if @event_loop end |
#thread_read ⇒ Object
this thread is responsible for reading the servers messages and dispatching them to responders normally this thread is alive as long as the client is connected to the server
292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 |
# File 'lib/butler/irc/client.rb', line 292 def thread_read while = @irc.read; process(); end on_disconnect(:disconnect) rescue Terminate => e # got the termination signal info("Terminating read thread") on_disconnect(:quit) rescue Errno::EPIPE => error # irc server closed connection exception(error) on_disconnect(:disconnect) rescue Exception => error exception(error) on_disconnect(:error) ensure @irc.close end |
#unsubscribe(id) ⇒ Object
unsubscribe a listener by id
153 154 155 156 157 158 159 |
# File 'lib/butler/irc/client.rb', line 153 def unsubscribe(id) @dispatch_lock.synchronize { @listener.delete(id) @listen[symbol].delete(item) @listen.delete(symbol) if @listen[symbol].empty? } end |
#wait_for(symbol, timeout = nil, &test) ⇒ Object
blocks current thread until a Message with symbol (optionally passing a test given as block) is received, returns the message received that matches. returns nil if it times out before a match
165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
# File 'lib/butler/irc/client.rb', line 165 def wait_for(symbol, timeout=nil, &test) timeout(timeout) { queue = Queue.new listener = subscribe(symbol) { |l, m| queue.push(m) } begin = queue.shift end until block_given? ? yield() : true } rescue Timeout::Error return nil ensure listener.unsubscribe end |
#whois(user) ⇒ Object
Do a whois on nick Returns an Butler::IRC::Whois-Struct
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 |
# File 'lib/butler/irc/client.rb', line 230 def whois(user) nick = user.to_str.strip_user_prefixes raise ArgumentError, "Invalid nick #{nick.inspect}" unless nick.valid_nickname? queue = Queue.new whois = Whois.new whois.exists = true [ :RPL_WHOISUSER, :RPL_WHOISSERVER, :RPL_WHOISIDLE, :RPL_ENDOFWHOIS, :RPL_UNIQOPIS, :RPL_WHOISCHANNELS, :RPL_IDENTIFIED_TO_SERVICES, :ERR_NOSUCHNICK, :RPL_REGISTERED_INFO ].each { |reply| filter(reply, 1, queue) } @irc.whois(nick) until ( = queue.shift).symbol == :RPL_ENDOFWHOIS case .symbol when :ERR_NOSUCHNICK whois.exists = false when :RPL_WHOISUSER whois.exists = true whois.nick = .nick whois.user = .user whois.host = .host whois.real = .real when :RPL_WHOISSERVER when :RPL_WHOISIDLE whois.exists = true whois.signon = .signon_time whois.idle = .seconds_idle when :RPL_UNIQOPIS when :RPL_WHOISCHANNELS whois.exists = true whois.channels = .channels when :RPL_REGISTERED_INFO whois.exists = true whois.registered = true end end return whois end |