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.
-
#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).
-
#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 FIXME, better way to implement the reason? use a block (yay)? 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
88 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 |
# File 'lib/butler/irc/socket.rb', line 88 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 = nil @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)
70 71 72 |
# File 'lib/butler/irc/socket.rb', line 70 def count @count end |
#eol ⇒ Object (readonly)
end-of-line used for communication
67 68 69 |
# File 'lib/butler/irc/socket.rb', line 67 def eol @eol end |
#host ⇒ Object (readonly)
the own host (nil if not supported)
65 66 67 |
# File 'lib/butler/irc/socket.rb', line 65 def host @host end |
#limit ⇒ Object (readonly)
contains limits for the protocol, burst times/counts etc.
73 74 75 |
# File 'lib/butler/irc/socket.rb', line 73 def limit @limit end |
#port ⇒ Object (readonly)
port used for connection
63 64 65 |
# File 'lib/butler/irc/socket.rb', line 63 def port @port end |
#server ⇒ Object (readonly)
server the instance is linked with
61 62 63 |
# File 'lib/butler/irc/socket.rb', line 61 def server @server end |
Instance Method Details
#action(message, *recipients) ⇒ Object
same as privmsg except it’s formatted for ACTION
232 233 234 235 236 237 238 |
# File 'lib/butler/irc/socket.rb', line 232 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’
298 299 300 301 |
# File 'lib/butler/irc/socket.rb', line 298 def away(reason="") return back if reason.empty? write("AWAY :#{reason}") end |
#back ⇒ Object
reset your away status to back
304 305 306 |
# File 'lib/butler/irc/socket.rb', line 304 def back write("AWAY") end |
#ban(mask, channel) ⇒ Object
Set ban in channel to mask
338 339 340 |
# File 'lib/butler/irc/socket.rb', line 338 def ban(mask, channel) write("MODE #{channel} +b #{mask}") end |
#close ⇒ Object
closes the connection to the irc-server
360 361 362 363 |
# File 'lib/butler/irc/socket.rb', line 360 def close raise "Socket not open" unless @socket @socket.close unless @socket.closed? end |
#connect ⇒ Object
connects to the server
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/butler/irc/socket.rb', line 116 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.
321 322 323 |
# File 'lib/butler/irc/socket.rb', line 321 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.
333 334 335 |
# File 'lib/butler/irc/socket.rb', line 333 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
208 209 210 |
# File 'lib/butler/irc/socket.rb', line 208 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)
203 204 205 |
# File 'lib/butler/irc/socket.rb', line 203 def identify(password) write("NS :IDENTIFY #{password}") end |
#inspect ⇒ Object
:nodoc:
365 366 367 368 369 370 371 372 373 374 375 |
# File 'lib/butler/irc/socket.rb', line 365 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.
264 265 266 267 268 269 270 271 272 273 |
# File 'lib/butler/irc/socket.rb', line 264 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
309 310 311 |
# File 'lib/butler/irc/socket.rb', line 309 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)
194 195 196 197 198 |
# File 'lib/butler/irc/socket.rb', line 194 def login(nickname, username, realname) connect unless @connected write("NICK #{nickname}") write("USER #{username} 0 * :#{realname}") end |
#nick(nick) ⇒ Object
set your own nick does NO verification/validation of any kind
293 294 295 |
# File 'lib/butler/irc/socket.rb', line 293 def nick(nick) write("NICK #{nick}") end |
#normalize_message(message, limit = :message_length) ⇒ Object
212 213 214 215 216 217 218 |
# File 'lib/butler/irc/socket.rb', line 212 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)
244 245 246 247 248 249 250 |
# File 'lib/butler/irc/socket.rb', line 244 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.
315 316 317 |
# File 'lib/butler/irc/socket.rb', line 315 def op(channel, *users) write("MODE #{channel} +#{'o'*users.length} #{users*' '}") end |
#part(reason = nil, *channels) ⇒ Object
part specified channels FIXME, better way to implement the reason? use a block (yay)? returns the channels parted from.
278 279 280 281 282 283 284 285 286 287 288 289 |
# File 'lib/butler/irc/socket.rb', line 278 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
253 254 255 256 257 258 259 |
# File 'lib/butler/irc/socket.rb', line 253 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.
223 224 225 226 227 228 229 |
# File 'lib/butler/irc/socket.rb', line 223 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
354 355 356 357 |
# File 'lib/butler/irc/socket.rb', line 354 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
135 136 137 138 139 140 141 142 |
# File 'lib/butler/irc/socket.rb', line 135 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.
327 328 329 |
# File 'lib/butler/irc/socket.rb', line 327 def voice(channel, *users) write("MODE #{channel} +#{'v'*users.length} #{users*' '}") end |
#who(channel) ⇒ Object
Send a “who” to channel
343 344 345 |
# File 'lib/butler/irc/socket.rb', line 343 def who(channel) write("WHO #{channel}") end |
#whois(nick) ⇒ Object
Send a “whois” to server
348 349 350 |
# File 'lib/butler/irc/socket.rb', line 348 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
149 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 |
# File 'lib/butler/irc/socket.rb', line 149 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 |