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 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#write 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
FIXME
mode commands don’t test for length and split up
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.
-
#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(mask, channel) ⇒ Object
Set ban in channel to mask.
-
#close ⇒ Object
closes the connection to the irc-server.
-
#connect ⇒ Object
connects to the server.
-
#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) ⇒ Object
log into the irc-server (and connect if necessary).
-
#mode(channel, mode) ⇒ Object
send a mode command to a channel.
-
#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.
-
#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(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 |
# 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) @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)
71 72 73 |
# File 'lib/butler/irc/socket.rb', line 71 def count @count end |
#eol ⇒ Object (readonly)
end-of-line used for communication
68 69 70 |
# File 'lib/butler/irc/socket.rb', line 68 def eol @eol end |
#host ⇒ Object (readonly)
the own host (nil if not supported)
66 67 68 |
# File 'lib/butler/irc/socket.rb', line 66 def host @host end |
#limit ⇒ Object (readonly)
contains limits for the protocol, burst times/counts etc.
74 75 76 |
# File 'lib/butler/irc/socket.rb', line 74 def limit @limit end |
#port ⇒ Object (readonly)
port used for connection
64 65 66 |
# File 'lib/butler/irc/socket.rb', line 64 def port @port end |
#server ⇒ Object (readonly)
server the instance is linked with
62 63 64 |
# File 'lib/butler/irc/socket.rb', line 62 def server @server end |
Instance Method Details
#action(message, *recipients) ⇒ Object
same as privmsg except it’s formatted for ACTION
234 235 236 237 238 239 240 |
# File 'lib/butler/irc/socket.rb', line 234 def action(, *recipients) ().each { || recipients.each { |recipient| write("PRIVMSG #{recipient} :"+(1.chr)+"ACTION "++(1.chr)) } } end |
#away(reason = "") ⇒ Object
set your status to away with reason ‘reason’
299 300 301 302 |
# File 'lib/butler/irc/socket.rb', line 299 def away(reason="") return back if reason.empty? write("AWAY :#{reason}") end |
#back ⇒ Object
reset your away status to back
305 306 307 |
# File 'lib/butler/irc/socket.rb', line 305 def back write("AWAY") end |
#ban(mask, channel) ⇒ Object
Set ban in channel to mask
344 345 346 |
# File 'lib/butler/irc/socket.rb', line 344 def ban(mask, channel) write("MODE #{channel} +b #{mask}") end |
#close ⇒ Object
closes the connection to the irc-server
366 367 368 369 |
# File 'lib/butler/irc/socket.rb', line 366 def close raise "Socket not open" unless @socket @socket.close unless @socket.closed? end |
#connect ⇒ Object
connects to the server
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/butler/irc/socket.rb', line 117 def connect @socket = TCPSocket.open(@server, @port) #, @host) info("Connected to #{@server}:#{@port} from #{@host || '<default>'}") 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 Exception error("Connection failed.") raise else @connected = true end |
#deop(channel, *users) ⇒ Object
Take Op from user in channel User can be a nick or IRC::User, either one or an array.
327 328 329 |
# File 'lib/butler/irc/socket.rb', line 327 def deop(channel, *users) write("MODE #{channel} -#{'o'*users.length} #{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.
339 340 341 |
# File 'lib/butler/irc/socket.rb', line 339 def devoice(channel, *users) write("MODE #{channel} -#{'v'*users.length} #{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)
210 211 212 |
# File 'lib/butler/irc/socket.rb', line 210 def ghost(nickname, password) write("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)
204 205 206 |
# File 'lib/butler/irc/socket.rb', line 204 def identify(password) write("NS :IDENTIFY #{password}") end |
#inspect ⇒ Object
:nodoc:
371 372 373 374 375 376 377 378 379 380 381 |
# File 'lib/butler/irc/socket.rb', line 371 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.
266 267 268 269 270 271 272 273 274 275 |
# File 'lib/butler/irc/socket.rb', line 266 def join(*channels) channels.map { |channel, password| if password then write("JOIN #{channel} #{password}") else write("JOIN #{channel}") end channel } end |
#kick(user, channel, reason) ⇒ Object
kick user in channel with reason
310 311 312 |
# File 'lib/butler/irc/socket.rb', line 310 def kick(user, channel, reason) write("KICK #{channel} #{user} :#{reason}") end |
#login(nickname, username, realname) ⇒ Object
log into the irc-server (and connect if necessary)
195 196 197 198 199 |
# File 'lib/butler/irc/socket.rb', line 195 def login(nickname, username, realname) connect unless @connected write("NICK #{nickname}") write("USER #{username} 0 * :#{realname}") end |
#mode(channel, mode) ⇒ Object
send a mode command to a channel
315 316 317 |
# File 'lib/butler/irc/socket.rb', line 315 def mode(channel, mode) write("MODE #{channel} #{mode}") end |
#nick(nick) ⇒ Object
set your own nick does NO verification/validation of any kind
294 295 296 |
# File 'lib/butler/irc/socket.rb', line 294 def nick(nick) write("NICK #{nick}") end |
#normalize_message(message, limit = :message_length) ⇒ Object
214 215 216 217 218 219 220 |
# File 'lib/butler/irc/socket.rb', line 214 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)
246 247 248 249 250 251 252 |
# File 'lib/butler/irc/socket.rb', line 246 def notice(, *recipients) ().each { || recipients.each { |recipient| write("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.
321 322 323 |
# File 'lib/butler/irc/socket.rb', line 321 def op(channel, *users) write("MODE #{channel} +#{'o'*users.length} #{users*' '}") end |
#part(reason = nil, *channels) ⇒ Object
part specified channels returns the channels parted from.
279 280 281 282 283 284 285 286 287 288 289 290 |
# File 'lib/butler/irc/socket.rb', line 279 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("PART #{channel} #{reason}") } end |
#pong(*args) ⇒ Object
send a pong
255 256 257 258 259 260 261 |
# File 'lib/butler/irc/socket.rb', line 255 def pong(*args) if args.empty? then write("PONG") else write("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.
225 226 227 228 229 230 231 |
# File 'lib/butler/irc/socket.rb', line 225 def privmsg(, *recipients) ().each { || recipients.each { |recipient| write("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
360 361 362 363 |
# File 'lib/butler/irc/socket.rb', line 360 def quit(reason="leaving", close=false) write("QUIT :#{reason}") close() if close end |
#read ⇒ Object
get next message (eol already chomped) from server, blocking, returns nil if closed
136 137 138 139 140 141 142 143 |
# File 'lib/butler/irc/socket.rb', line 136 def read @count[:read] += 1 if m = @socket.gets(@eol) then m.chomp(@eol) else nil end end |
#voice(channel, *users) ⇒ Object
Give voice to user in channel User can be a nick or IRC::User, either one or an array.
333 334 335 |
# File 'lib/butler/irc/socket.rb', line 333 def voice(channel, *users) write("MODE #{channel} +#{'v'*users.length} #{users*' '}") end |
#who(channel) ⇒ Object
Send a “who” to channel
349 350 351 |
# File 'lib/butler/irc/socket.rb', line 349 def who(channel) write("WHO #{channel}") end |
#whois(nick) ⇒ Object
Send a “whois” to server
354 355 356 |
# File 'lib/butler/irc/socket.rb', line 354 def whois(nick) write("WHOIS #{nick}") end |
#write(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
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 180 181 182 183 184 185 186 187 188 189 190 191 192 |
# File 'lib/butler/irc/socket.rb', line 150 def write(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 } rescue IOError error("Writing #{data.inspect} failed") raise end |