Class: Butler::IRC::Socket
- Inherits:
-
Object
- Object
- Butler::IRC::Socket
- Includes:
- Log::Comfort
- Defined in:
- lib/butler/irc/socket.rb
Overview
Description
Butler::IRC::Socket is a TCPSocket, retrofitted for communication with IRC-Servers. It provides specialized methods for sending messages to IRC-Server. All methods are safe to be used with Butler::IRC::* Objects (e.g. all parameters expecting a nickname will accept an Butler::IRC::User as well). It will adhere to its limit-settings, which will prevent from sending too many messages in a too short time to avoid excess flooding. Butler::IRC::Socket#write_with_eol is the only synchronized method, since all other methods build up on it, IRC::Socket should be safe in threaded environments. Butler::IRC::Socket#read is NOT synchronized, so unless you read from only a single thread, statistics might get messed up. Length limits can only be safely guaranteed by specialized write methods, Butler::IRC::Socket#read from only will just warn and send the overlength message. If you are looking for queries (commands that get an answer from the server) take a look at Butler::IRC::Client.
Synopsis
irc = Butler::IRC::Socket.new(‘irc.freenode.org’, :port => 6667, :charset => ‘ISO-8859-1’) irc.connect irc.login(‘your_nickname’, ‘YourUser’, ‘Your realname’, [“#channel1”, “#channel2”]) irc.join(“#channel3”) irc.part(“#channel3”) irc.privmsg(“Hi all of you in #channel1!”, “#channel1”) irc.close
Notes
Errno::EHOSTUNREACH: server not reached Errno::ECONNREFUSED: server is up, but refuses connection Errno::ECONNRESET: connection works, server did not yet accept connection, resets after Errno::EPIPE: writing to a server-side closed connection, nil on gets, connection was terminated
Constant Summary collapse
- VERSION =
"1.0.0"
- OptionsDefault =
{ :port => 6667, :eol => "\r\n", :host => nil, }
Instance Attribute Summary collapse
-
#count ⇒ Object
readonly
contains various counters, such as :received, :sent (lines).
-
#eol ⇒ Object
readonly
end-of-line used for communication.
-
#host ⇒ Object
readonly
the own host (nil if not supported).
-
#limit ⇒ Object
readonly
contains limits for the protocol, burst times/counts etc.
-
#log_out ⇒ Object
log raw out, will use log_out.puts(raw).
-
#port ⇒ Object
readonly
port used for connection.
-
#server ⇒ Object
readonly
server the instance is linked with.
Attributes included from Log::Comfort
Instance Method Summary collapse
-
#action(message, *recipients) ⇒ Object
same as privmsg except it’s formatted for ACTION.
-
#away(reason = "") ⇒ Object
set your status to away with reason ‘reason’.
-
#back ⇒ Object
reset your away status to back.
-
#ban(channel, *masks) ⇒ Object
Set ban in channel to mask.
-
#close ⇒ Object
closes the connection to the irc-server.
-
#connect ⇒ Object
connects to the server.
- #connected? ⇒ Boolean
-
#deop(channel, *users) ⇒ Object
Take Op from user in channel User can be a nick or IRC::User, either one or an array.
-
#devoice(channel, *users) ⇒ Object
Take voice from user in channel.
-
#ghost(nickname, password) ⇒ Object
FIXME, figure out what the server supports, possibly requires it to be moved to Butler::IRC::Client (to allow ghosting, nickchange, identify).
-
#identify(password) ⇒ Object
identify nickname to nickserv FIXME, figure out what the server supports, possibly requires it to be moved to Butler::IRC::Client (to allow ghosting, nickchange, identify).
-
#initialize(server, options = {}) ⇒ Socket
constructor
Initialize properties, doesn’t connect automatically options: * :server: ip/domain of server (overrides a given server parameter) * :port: port to connect on, defaults to 6667 * :eol: what character sequence terminates messages, defaults to rn * :host: what host address to bind to, defaults to nil.
-
#inspect ⇒ Object
:nodoc:.
-
#join(*channels) ⇒ Object
join specified channels use an array [channel, password] to join password-protected channels returns the channels joined.
-
#kick(user, channel, reason) ⇒ Object
kick user in channel with reason.
-
#login(nickname, username, realname, serverpass = nil) ⇒ Object
log into the irc-server (and connect if necessary).
-
#mode(channel, mode) ⇒ Object
send a mode command to a channel.
-
#multiple_mode(channel, pre, flag, targets) ⇒ Object
Give Op to user in channel User can be a nick or IRC::User, either one or an array.
-
#nick(nick) ⇒ Object
set your own nick does NO verification/validation of any kind.
- #normalize_message(message, limit = :message_length) ⇒ Object
-
#notice(message, *recipients) ⇒ Object
sends a notice to receiver (or multiple if receiver is array of receivers) formatted=true allows usage of ![]-format commands (see IRCmessage.getFormatted) messages containing newline automatically get splitted up into multiple messages.
-
#op(channel, *users) ⇒ Object
Give Op to user in channel User can be a nick or IRC::User, either one or an array.
-
#part(reason = nil, *channels) ⇒ Object
part specified channels returns the channels parted from.
-
#pong(*args) ⇒ Object
send a pong.
-
#privmsg(message, *recipients) ⇒ Object
sends a privmsg to given user or channel (or multiple) messages containing newline or exceeding @limit are automatically splitted into multiple messages.
-
#quit(reason = "leaving", close = false) ⇒ Object
send the quit message to the server if you set close to true it will also close the socket.
-
#read ⇒ Object
get next message (eol already chomped) from server, blocking, returns nil if closed.
-
#unban(channel, *masks) ⇒ Object
Remove ban in channel to mask.
-
#voice(channel, *users) ⇒ Object
Give voice to user in channel User can be a nick or IRC::User, either one or an array.
-
#who(channel) ⇒ Object
Send a “who” to channel.
-
#whois(nick) ⇒ Object
Send a “whois” to server.
-
#write_with_eol(data) ⇒ Object
Send a raw message to irc, eol will be appended Use specialized methods instead if possible since they will releave you from several tasks like translating newlines, take care of overlength messages etc.
Methods included from Log::Comfort
#debug, #error, #exception, #fail, #info, #log, #warn
Constructor Details
#initialize(server, options = {}) ⇒ Socket
Initialize properties, doesn’t connect automatically options:
-
:server: ip/domain of server (overrides a given server parameter)
-
:port: port to connect on, defaults to 6667
-
:eol: what character sequence terminates messages, defaults to rn
-
:host: what host address to bind to, defaults to nil
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/butler/irc/socket.rb', line 89 def initialize(server, ={}) = OptionsDefault.merge() @logger = .delete(:log) @server = server # options.delete(:server) @port = .delete(:port) @eol = .delete(:eol).dup.freeze @host = [:host] ? .delete(:host).dup.freeze : .delete(:host) @log_out = nil @last_sent = Time.new() @count = Hash.new(0) @limit = OpenStruct.new({ :message_length => 300, # max. length of a text message (e.g. in notice, privmsg) sent to server :raw_length => 400, # max. length of a raw message sent to server :burst => 4, # max. messages that can be sent with send_delay (0 = infinite) :burst2 => 20, # max. messages that can be sent with send_delay (0 = infinite) :send_delay => 0.1, # minimum delay between each message :burst_delay => 1.5, # delay after a burst :burst2_delay => 15, # delay after a burst2 }) @limit.each { |key, default| @limit[key] = .delete(key) if .has_key?(key) } @mutex = Mutex.new @socket = Diagnostics.new(self, :write => [NoMethodError, "Must connect first to write to the socket"]) @connected = false raise ArgumentError, "Unknown arguments: #{.keys.inspect}" unless .empty? end |
Instance Attribute Details
#count ⇒ Object (readonly)
contains various counters, such as :received, :sent (lines)
68 69 70 |
# File 'lib/butler/irc/socket.rb', line 68 def count @count end |
#eol ⇒ Object (readonly)
end-of-line used for communication
65 66 67 |
# File 'lib/butler/irc/socket.rb', line 65 def eol @eol end |
#host ⇒ Object (readonly)
the own host (nil if not supported)
63 64 65 |
# File 'lib/butler/irc/socket.rb', line 63 def host @host end |
#limit ⇒ Object (readonly)
contains limits for the protocol, burst times/counts etc.
71 72 73 |
# File 'lib/butler/irc/socket.rb', line 71 def limit @limit end |
#log_out ⇒ Object
log raw out, will use log_out.puts(raw)
74 75 76 |
# File 'lib/butler/irc/socket.rb', line 74 def log_out @log_out end |
#port ⇒ Object (readonly)
port used for connection
61 62 63 |
# File 'lib/butler/irc/socket.rb', line 61 def port @port end |
#server ⇒ Object (readonly)
server the instance is linked with
59 60 61 |
# File 'lib/butler/irc/socket.rb', line 59 def server @server end |
Instance Method Details
#action(message, *recipients) ⇒ Object
same as privmsg except it’s formatted for ACTION
245 246 247 248 249 250 251 |
# File 'lib/butler/irc/socket.rb', line 245 def action(, *recipients) ().each { || recipients.each { |recipient| write_with_eol("PRIVMSG #{recipient} :"+(1.chr)+"ACTION "++(1.chr)) } } end |
#away(reason = "") ⇒ Object
set your status to away with reason ‘reason’
310 311 312 313 |
# File 'lib/butler/irc/socket.rb', line 310 def away(reason="") return back if reason.empty? write_with_eol("AWAY :#{reason}") end |
#back ⇒ Object
reset your away status to back
316 317 318 |
# File 'lib/butler/irc/socket.rb', line 316 def back write_with_eol("AWAY") end |
#ban(channel, *masks) ⇒ Object
Set ban in channel to mask
364 365 366 |
# File 'lib/butler/irc/socket.rb', line 364 def ban(channel, *masks) multiple_mode(channel, '+', 'b', masks) end |
#close ⇒ Object
closes the connection to the irc-server
391 392 393 394 |
# File 'lib/butler/irc/socket.rb', line 391 def close raise "Socket not open" unless @socket @socket.close unless @socket.closed? end |
#connect ⇒ Object
connects to the server
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
# File 'lib/butler/irc/socket.rb', line 122 def connect info("Connecting to #{@server} on port #{@port} from #{@host || '<default>'}") @socket = TCPSocket.open(@server, @port) #, @host) info("Successfully connected") rescue ArgumentError => error if @host then warn("host-parameter is not supported by your ruby version. Parameter discarted.") @host = nil retry else raise end rescue Interrupt raise rescue Exception error("Connection failed.") raise else @connected = true end |
#connected? ⇒ Boolean
117 118 119 |
# File 'lib/butler/irc/socket.rb', line 117 def connected? @connected end |
#deop(channel, *users) ⇒ Object
Take Op from user in channel User can be a nick or IRC::User, either one or an array.
347 348 349 |
# File 'lib/butler/irc/socket.rb', line 347 def deop(channel, *users) multiple_mode(channel, '-', 'o', users) end |
#devoice(channel, *users) ⇒ Object
Take voice from user in channel. User can be a nick or IRC::User, either one or an array.
359 360 361 |
# File 'lib/butler/irc/socket.rb', line 359 def devoice(channel, *users) multiple_mode(channel, '-', 'v', users) end |
#ghost(nickname, password) ⇒ Object
FIXME, figure out what the server supports, possibly requires it to be moved to Butler::IRC::Client (to allow ghosting, nickchange, identify)
221 222 223 |
# File 'lib/butler/irc/socket.rb', line 221 def ghost(nickname, password) write_with_eol("NS :GHOST #{nickname} #{password}") end |
#identify(password) ⇒ Object
identify nickname to nickserv FIXME, figure out what the server supports, possibly requires it to be moved to Butler::IRC::Client (to allow ghosting, nickchange, identify)
215 216 217 |
# File 'lib/butler/irc/socket.rb', line 215 def identify(password) write_with_eol("NS :IDENTIFY #{password}") end |
#inspect ⇒ Object
:nodoc:
396 397 398 399 400 401 402 403 404 405 406 |
# File 'lib/butler/irc/socket.rb', line 396 def inspect # :nodoc: "#<%s:0x%08x %s:%s from %s using '%s', stats: %s>" % [ self.class, object_id << 1, @server, @port, @host || "<default>", @eol.inspect[1..-2], @count.inspect ] end |
#join(*channels) ⇒ Object
join specified channels use an array [channel, password] to join password-protected channels returns the channels joined.
277 278 279 280 281 282 283 284 285 286 |
# File 'lib/butler/irc/socket.rb', line 277 def join(*channels) channels.map { |channel, password| if password then write_with_eol("JOIN #{channel} #{password}") else write_with_eol("JOIN #{channel}") end channel } end |
#kick(user, channel, reason) ⇒ Object
kick user in channel with reason
321 322 323 |
# File 'lib/butler/irc/socket.rb', line 321 def kick(user, channel, reason) write_with_eol("KICK #{channel} #{user} :#{reason}") end |
#login(nickname, username, realname, serverpass = nil) ⇒ Object
log into the irc-server (and connect if necessary)
205 206 207 208 209 210 |
# File 'lib/butler/irc/socket.rb', line 205 def login(nickname, username, realname, serverpass=nil) connect unless @connected write_with_eol("PASS #{serverpass}") if serverpass write_with_eol("NICK #{nickname}") write_with_eol("USER #{username} 0 * :#{realname}") end |
#mode(channel, mode) ⇒ Object
send a mode command to a channel
326 327 328 |
# File 'lib/butler/irc/socket.rb', line 326 def mode(channel, mode) write_with_eol("MODE #{channel} #{mode}") end |
#multiple_mode(channel, pre, flag, targets) ⇒ Object
Give Op to user in channel User can be a nick or IRC::User, either one or an array.
332 333 334 335 336 337 |
# File 'lib/butler/irc/socket.rb', line 332 def multiple_mode(channel, pre, flag, targets) (0...targets.length).step(10) { |i| slice = targets[i,10] write_with_eol("MODE #{channel} +#{flag*slice.length} #{slice*' '}") } end |
#nick(nick) ⇒ Object
set your own nick does NO verification/validation of any kind
305 306 307 |
# File 'lib/butler/irc/socket.rb', line 305 def nick(nick) write_with_eol("NICK #{nick}") end |
#normalize_message(message, limit = :message_length) ⇒ Object
225 226 227 228 229 230 231 |
# File 'lib/butler/irc/socket.rb', line 225 def (, limit=:message_length) = [] .split(/\n/).each { |line| .concat(line.chunks(@limit[limit])) } end |
#notice(message, *recipients) ⇒ Object
sends a notice to receiver (or multiple if receiver is array of receivers) formatted=true allows usage of ![]-format commands (see IRCmessage.getFormatted) messages containing newline automatically get splitted up into multiple messages. Too long messages will be tokenized into fitting sized messages (see @limit)
257 258 259 260 261 262 263 |
# File 'lib/butler/irc/socket.rb', line 257 def notice(, *recipients) ().each { || recipients.each { |recipient| write_with_eol("NOTICE #{recipient} :#{}") } } end |
#op(channel, *users) ⇒ Object
Give Op to user in channel User can be a nick or IRC::User, either one or an array.
341 342 343 |
# File 'lib/butler/irc/socket.rb', line 341 def op(channel, *users) multiple_mode(channel, '+', 'o', users) end |
#part(reason = nil, *channels) ⇒ Object
part specified channels returns the channels parted from.
290 291 292 293 294 295 296 297 298 299 300 301 |
# File 'lib/butler/irc/socket.rb', line 290 def part(reason=nil, *channels) if channels.empty? channels = [reason] reason = nil end reason ||= "leaving" # some servers still can't process lists of channels in part channels.each { |channel| write_with_eol("PART #{channel} #{reason}") } end |
#pong(*args) ⇒ Object
send a pong
266 267 268 269 270 271 272 |
# File 'lib/butler/irc/socket.rb', line 266 def pong(*args) if args.empty? then write_with_eol("PONG") else write_with_eol("PONG #{args.join(' ')}") end end |
#privmsg(message, *recipients) ⇒ Object
sends a privmsg to given user or channel (or multiple) messages containing newline or exceeding @limit are automatically splitted into multiple messages.
236 237 238 239 240 241 242 |
# File 'lib/butler/irc/socket.rb', line 236 def privmsg(, *recipients) ().each { || recipients.each { |recipient| write_with_eol("PRIVMSG #{recipient} :#{}") } } end |
#quit(reason = "leaving", close = false) ⇒ Object
send the quit message to the server if you set close to true it will also close the socket
385 386 387 388 |
# File 'lib/butler/irc/socket.rb', line 385 def quit(reason="leaving", close=false) write_with_eol("QUIT :#{reason}") close() if close end |
#read ⇒ Object
get next message (eol already chomped) from server, blocking, returns nil if closed
144 145 146 147 148 149 150 151 152 |
# File 'lib/butler/irc/socket.rb', line 144 def read @count[:read] += 1 if m = @socket.gets(@eol) then m.chomp(@eol) else @connected = false nil end end |
#unban(channel, *masks) ⇒ Object
Remove ban in channel to mask
369 370 371 |
# File 'lib/butler/irc/socket.rb', line 369 def unban(channel, *masks) multiple_mode(channel, '-', 'b', masks) end |
#voice(channel, *users) ⇒ Object
Give voice to user in channel User can be a nick or IRC::User, either one or an array.
353 354 355 |
# File 'lib/butler/irc/socket.rb', line 353 def voice(channel, *users) multiple_mode(channel, '+', 'v', users) end |
#who(channel) ⇒ Object
Send a “who” to channel
374 375 376 |
# File 'lib/butler/irc/socket.rb', line 374 def who(channel) write_with_eol("WHO #{channel}") end |
#whois(nick) ⇒ Object
Send a “whois” to server
379 380 381 |
# File 'lib/butler/irc/socket.rb', line 379 def whois(nick) write_with_eol("WHOIS #{nick}") end |
#write_with_eol(data) ⇒ Object
Send a raw message to irc, eol will be appended Use specialized methods instead if possible since they will releave you from several tasks like translating newlines, take care of overlength messages etc. FIXME, wrong methodname, write implies nothing is appended
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 |
# File 'lib/butler/irc/socket.rb', line 159 def write_with_eol(data) @mutex.synchronize { warn("Raw too long (#{data.length} instead of #{@limit[:raw_length]})") if (data.length > @limit.raw_length) now = Time.now # keep delay between single (bursted) messages sleeptime = @limit.send_delay-(now-@last_sent) if sleeptime > 0 then sleep(sleeptime) now += sleeptime end # keep delay after a burst (1) if (@count[:burst] >= @limit[:burst]) then sleeptime = @limit.burst_delay-(now-@last_sent) if sleeptime > 0 then sleep(sleeptime) now += sleeptime end @count[:burst] = 0 end # keep delay after a burst (2) if (@count[:burst2] >= @limit[:burst2]) then sleeptime = @limit.burst2_delay-(now-@last_sent) if sleeptime > 0 then sleep(sleeptime) now += sleeptime end @count[:burst2] = 0 end # send data and update data @last_sent = Time.new @socket.write(data+@eol) @count[:burst] += 1 @count[:burst2] += 1 @count[:sent] += 1 @log_out.puts(data) if @log_out } rescue IOError error("Writing #{data.inspect} failed") raise end |