Class: Butler::IRC::Client
- Inherits:
-
Object
- Object
- Butler::IRC::Client
- Includes:
- Log::Comfort
- 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.
Attributes included from Log::Comfort
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.
-
#load_command_set(*sets) ⇒ Object
Load an additional command-set for @parser.
-
#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.
Methods included from Log::Comfort
#debug, #error, #exception, #fail, #info, #log, #warn
Constructor Details
#initialize(server, options = {}, &on_disconnect) ⇒ Client
Returns a new instance of Client.
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/butler/irc/client.rb', line 104 def initialize(server, ={}, &on_disconnect) = DefaultOptions.merge() @logger = nil @users = Users.new(self) @channels = Channels.new(self) @users.channels = @channels @channels.users = @users @parser = Parser.new(self, @users, @channels, "rfc2812", "generic") @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(.delete(:server) || 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
93 94 95 |
# File 'lib/butler/irc/client.rb', line 93 def channel_charset @channel_charset end |
#channels ⇒ Object (readonly)
Returns the value of attribute channels.
95 96 97 |
# File 'lib/butler/irc/client.rb', line 95 def channels @channels end |
#client_charset ⇒ Object
The charset the client uses
85 86 87 |
# File 'lib/butler/irc/client.rb', line 85 def client_charset @client_charset end |
#irc ⇒ Object (readonly)
The Butler::IRC::Socket, most send commands have to be used on this
99 100 101 |
# File 'lib/butler/irc/client.rb', line 99 def irc @irc end |
#myself ⇒ Object (readonly)
The user representing the bot
102 103 104 |
# File 'lib/butler/irc/client.rb', line 102 def myself @myself end |
#server_charset ⇒ Object
The charset the server needs messages sent to him and sends message to this client in.
89 90 91 |
# File 'lib/butler/irc/client.rb', line 89 def server_charset @server_charset end |
#users ⇒ Object (readonly)
Returns the value of attribute users.
96 97 98 |
# File 'lib/butler/irc/client.rb', line 96 def users @users end |
Instance Method Details
#event_loop(priority = -1)) ⇒ Object
319 320 321 322 323 324 325 326 327 328 |
# File 'lib/butler/irc/client.rb', line 319 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)
192 193 194 195 196 197 198 199 200 201 202 |
# File 'lib/butler/irc/client.rb', line 192 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:
350 351 352 353 354 355 356 |
# File 'lib/butler/irc/client.rb', line 350 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
230 231 232 233 234 |
# File 'lib/butler/irc/client.rb', line 230 def join(*args) @irc.join(*args).each { |channel| @irc.who(channel) } end |
#load_command_set(*sets) ⇒ Object
Load an additional command-set for @parser
133 134 135 |
# File 'lib/butler/irc/client.rb', line 133 def load_command_set(*sets) @parser.commands.load(*sets) end |
#login(nick, user, real) ⇒ Object
login under nick, user, real
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 |
# File 'lib/butler/irc/client.rb', line 205 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
316 317 |
# File 'lib/butler/irc/client.rb', line 316 def on_disconnect(reason) end |
#process(message) ⇒ Object
process a Butler::IRC::Message, normally fed from thread_read.
335 336 337 338 339 340 341 342 343 344 345 346 347 348 |
# File 'lib/butler/irc/client.rb', line 335 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
284 285 286 287 288 |
# File 'lib/butler/irc/client.rb', line 284 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
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/butler/irc/client.rb', line 142 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
292 293 294 295 |
# File 'lib/butler/irc/client.rb', line 292 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
330 331 332 |
# File 'lib/butler/irc/client.rb', line 330 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
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 |
# File 'lib/butler/irc/client.rb', line 300 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
161 162 163 164 165 166 167 |
# File 'lib/butler/irc/client.rb', line 161 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
173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
# File 'lib/butler/irc/client.rb', line 173 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
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 274 275 276 277 278 279 280 281 |
# File 'lib/butler/irc/client.rb', line 238 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 |