Class: Butler::IRC::Client
- Inherits:
-
Object
- Object
- Butler::IRC::Client
- Includes:
- Log::Comfort
- Defined in:
- lib/butler/irc/client.rb,
lib/butler/irc/client/filter.rb,
lib/butler/irc/client/listener.rb,
lib/butler/irc/client/listenerlist.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
irc_client.irc.notice("Welcome to #{.channel}!", .from)
end
puts "received: #{}"
}
puts "If this point is reached, client has ended"
Defined Under Namespace
Modules: Filter Classes: Listener, ListenerList, 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
The channels the client participates.
-
#client_charset ⇒ Object
The charset the client uses.
-
#disconnect_callback ⇒ Object
A callback invoked on disconnects, accepts one argument: reason, which can be * :quit: you told the client to quit * :disconnect: the server disconnected your client * :error: an error occurred in the read-thread (shouldn’t happen).
-
#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
The users visible to the client.
Attributes included from Log::Comfort
Instance Method Summary collapse
-
#dispatch(message) ⇒ Object
dispatch a message.
- #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
Arguments: * server: the server to connect to.
-
#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, serverpass = nil) ⇒ Object
login under nick, user, real.
-
#on_disconnect(reason) ⇒ Object
Called on disconnects, see disconnect_callback attribute documentation.
-
#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(symbols = nil, priority = 0, *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.
-
#subscribe_listener(listener) ⇒ Object
subscribe a Listener instance (or anything that emulates its interface).
-
#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(listener) ⇒ Object
unsubscribe a listener.
-
#wait_for(symbol, timeout = nil, opt = {}, &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
Arguments:
* server: the server to connect to
98 99 100 101 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 128 129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/butler/irc/client.rb', line 98 def initialize(server, ={}, &on_disconnect) = DefaultOptions.merge() @disconnect_callback = @logger = nil @users = UserList.new(self) @channels = ChannelList.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 @listener = ListenerList.new @event_loop = Queue.new @thread_read = nil @myself = @users.myself subscribe(:PING, 100) { |listener, | @irc.pong(.pong) } subscribe(:ISUPPORT) { |listener, | isupport = {} @parser.isupport.to_hash.each { |key, value| isupport[key.to_s] = value } .support.each { |key,value| isupport[key.downcase] = value } if .support.has_key?("PREFIX") then isupport["prefixes"] = .support["PREFIX"].values.join('') end @parser.reset(isupport) @irc.write_with_eol("CAPAB IDENTIFY-MSG") if @parser.isupport.capab and !@parser.msg_identify } subscribe(:RPL_IDENTIFY_MSG) { |listener, | @parser.msg_identify = true } end |
Instance Attribute Details
#channel_charset ⇒ Object (readonly)
The charsets of individual channel, only set if they differ from server_charset
76 77 78 |
# File 'lib/butler/irc/client.rb', line 76 def channel_charset @channel_charset end |
#channels ⇒ Object (readonly)
The channels the client participates
79 80 81 |
# File 'lib/butler/irc/client.rb', line 79 def channels @channels end |
#client_charset ⇒ Object
The charset the client uses
68 69 70 |
# File 'lib/butler/irc/client.rb', line 68 def client_charset @client_charset end |
#disconnect_callback ⇒ Object
A callback invoked on disconnects, accepts one argument: reason, which can be
* :quit: you told the client to quit
* :disconnect: the server disconnected your client
* :error: an error occurred in the read-thread (shouldn't happen)
94 95 96 |
# File 'lib/butler/irc/client.rb', line 94 def disconnect_callback @disconnect_callback end |
#irc ⇒ Object (readonly)
The Butler::IRC::Socket, most send commands have to be used on this
85 86 87 |
# File 'lib/butler/irc/client.rb', line 85 def irc @irc end |
#myself ⇒ Object (readonly)
The user representing the bot
88 89 90 |
# File 'lib/butler/irc/client.rb', line 88 def myself @myself end |
#server_charset ⇒ Object
The charset the server needs messages sent to him and sends message to this client in.
72 73 74 |
# File 'lib/butler/irc/client.rb', line 72 def server_charset @server_charset end |
#users ⇒ Object (readonly)
The users visible to the client
82 83 84 |
# File 'lib/butler/irc/client.rb', line 82 def users @users end |
Instance Method Details
#dispatch(message) ⇒ Object
dispatch a message
349 350 351 352 353 354 |
# File 'lib/butler/irc/client.rb', line 349 def dispatch() .transcode!(@channel_charset[.channel], @client_charset) @listener.synchronized_each_for(.symbol) { |listener| listener.call() } end |
#event_loop(priority = -1)) ⇒ Object
324 325 326 327 328 329 330 331 332 333 |
# File 'lib/butler/irc/client.rb', line 324 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)
193 194 195 196 197 198 199 200 201 202 203 |
# File 'lib/butler/irc/client.rb', line 193 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:
356 357 358 359 360 361 362 |
# File 'lib/butler/irc/client.rb', line 356 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
231 232 233 234 235 |
# File 'lib/butler/irc/client.rb', line 231 def join(*args) @irc.join(*args).each { |channel| @irc.who(channel) } end |
#load_command_set(*sets) ⇒ Object
Load an additional command-set for @parser
143 144 145 |
# File 'lib/butler/irc/client.rb', line 143 def load_command_set(*sets) @parser.commands.load(*sets) end |
#login(nick, user, real, serverpass = nil) ⇒ Object
login under nick, user, real
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 |
# File 'lib/butler/irc/client.rb', line 206 def login(nick, user, real, serverpass=nil) 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, serverpass) queue.shift } true ensure queue.unsubscribe if queue nick_change.unsubscribe if nick_change end |
#on_disconnect(reason) ⇒ Object
Called on disconnects, see disconnect_callback attribute documentation
320 321 322 |
# File 'lib/butler/irc/client.rb', line 320 def on_disconnect(reason) @disconnect_callback.call(reasons) if @disconnect_callback end |
#process(message) ⇒ Object
process a Butler::IRC::Message, normally fed from thread_read.
340 341 342 343 344 345 346 |
# File 'lib/butler/irc/client.rb', line 340 def process() dispatch(@parser.()) 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
285 286 287 288 289 |
# File 'lib/butler/irc/client.rb', line 285 def quit(reason=nil) @irc.quit(reason) terminate @irc.close end |
#subscribe(symbols = nil, priority = 0, *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
152 153 154 155 156 |
# File 'lib/butler/irc/client.rb', line 152 def subscribe(symbols=nil, priority=0, *args, &callback) listener = Listener.new(symbols, priority, *args, &callback) @listener.subscribe(listener) listener end |
#subscribe_listener(listener) ⇒ Object
subscribe a Listener instance (or anything that emulates its interface)
159 160 161 |
# File 'lib/butler/irc/client.rb', line 159 def subscribe_listener(listener) @listener.subscribe(listener) end |
#terminate(stop_reading = true) ⇒ Object
terminate all processing and reading, see Client#quit
293 294 295 296 |
# File 'lib/butler/irc/client.rb', line 293 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
335 336 337 |
# File 'lib/butler/irc/client.rb', line 335 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
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 |
# File 'lib/butler/irc/client.rb', line 301 def thread_read while = @irc.read; process(); end @irc.close on_disconnect(:disconnect) rescue Terminate => e # got the termination signal info("Terminating read thread") @irc.close on_disconnect(:quit) rescue Errno::EPIPE => error # irc server closed connection exception(error) @irc.close on_disconnect(:disconnect) rescue Exception => error exception(error) @irc.close on_disconnect(:error) end |
#unsubscribe(listener) ⇒ Object
unsubscribe a listener
164 165 166 |
# File 'lib/butler/irc/client.rb', line 164 def unsubscribe(listener) @listener.unsubscribe(listener) end |
#wait_for(symbol, timeout = nil, opt = {}, &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
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 |
# File 'lib/butler/irc/client.rb', line 172 def wait_for(symbol, timeout=nil, opt={}, &test) listener = nil timeout(timeout) { queue = Queue.new listener = subscribe(symbol) { |l, m| queue.push(m) } opt[:prepare].call if opt.has_key?(:prepare) 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
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 282 |
# File 'lib/butler/irc/client.rb', line 239 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 |