class Disconnected < RuntimeError end class Ircd require 'socket' require 'chan.rb' require 'nick.rb' attr_reader :connected, :online # @handlers => # 'online' [] # 'disconnect' [] # 'selfjoin' [chan] # 'selfpart' [chan] # 'selfnick' [oldnick] # 'kicked' [chan, who] # 'join' [chan, who] # 'part' [chan, who] # 'quit' [who] # 'kick' [chan, who, victim] # 'invited' [who, chanlist] # 'nick' [who, oldnick] # 'mode' [chan, who, modestr] # 'op' [chan, who, newops] # 'deop' [chan, who, oldops] # 'privmsg' [target, who, msg] # 'notice' [target, who, msg] # 'raw' [rawstr] attr_reader :server, :port, :nick, :nicklist, :chanlist, :connected, :handlers, :online @user, @wishnick = nil def reset if @socket @socket.shutdown 2 @socket.close @socket = nil end @nick = Nick.new(self, @wishnick, '') @nicklist = [@nick] @chanlist = Array.new @sendlist = Array.new @connected = false @online = false @sent_user = false @quitting = false end def initialize(server, port, user, nick) @server, @port, @user, @wishnick = server, port, user, nick @nohandler = Proc.new {} @handlers = Hash.new(@nohandler) reset end def sender # the code for the thread designated to send messages @lastcomm = Time.now.tv_sec while @socket n = Time.now.tv_sec s = @sendlist.shift if s puts '-->: ' + s begin @socket.send(s + "\r\n", 0) @lastcomm = n rescue Errno::EPIPE reset @handlers['disconnected'].call end else # 3 minutes @sendlist << 'PING :connected' if n - @lastcomm > 180 end sleep 0.01 n = Time.now.tv_sec end end def send(s) # TODO: remplacer par timeout @sendlist << s end def to_str "\n -i- #{server}:#{port} you are #{@nick}" + "\n --- chanlist : \n " + @chanlist.join("\n ") + "\n --- nicklist : \n " + @nicklist.join("\n ") end alias to_str to_s def connect() # returns on disconnection @socket = TCPSocket.open(@server, @port) @sender = Thread.new { sender } @connected = true while @socket and not @socket.eof s = @socket.gets.chomp @lastcomm = Time.now.tv_sec handle s @handlers['raw'].call s end reset @sender.join @handlers['disconnected'].call end def quit(msg='quitting') @quitting = true send 'QUIT :' + msg end def nick=(newnick) @wishnick = newnick send 'NICK ' + @wishnick end def findchan(name) @lastfindchan = (@lastfindchan and @lastfindchan.name==name.downcase) ? @lastfindchan : @chanlist.find { |c| c.name == name.downcase} if block_given? yield @lastfindchan if @lastfindchan else unless @lastfindchan puts ' -!- searched and not found ' + name raise 'No such chan ' + name end end @lastfindchan end def findnick(nick, userhost='') @lastfindnick = (@lastfindnick and @lastfindnick.nick == nick.downcase) ? @lastfindnick : @nicklist.find { |n| n.nick == nick.downcase } if (userhost != '' and @lastfindnick and @lastfindnick.userhost != '' and @lastfindnick.userhost != userhost.downcase) puts " -!- warning: searching for #{nick}@#{userhost}, i found #{@lastfindnick.nick}@#{@lastfindnick.userhost}" end yield @lastfindnick if block_given? and @lastfindnick @lastfindnick end def handle(s) s =~ /^(:(\S+)\s+)?(\S.*?)[\r]?$/ whom = $2 cmd = $3 case whom when @server case cmd when /^NOTICE AUTH/ # first greetings from server if (!@sent_user) send 'USER ' + @user send 'NICK ' + @wishnick @sent_user = 1 end when /^221 #{@nick.nick} (.*)/ # user mode @nick.setmode $1 when /^302 #{@nick.nick} :(\S+)=\+(.+@.+)/ # userhost reply findnick($1) { |guy| guy.userhost = $2 } when /^315 #{@nick.nick} (\S+)/ # end of /WHO chan = findchan $1 send 'MODE ' + chan.name unless chan.sync when /^324 #{@nick.nick} (\S+) (\S.*)/ # channel mode chan = findchan $1 chan.setmode $2 chan.sync = true when /^329 #{@nick.nick} (\S+) (\d+)/ # channel creation time when /^332 #{@nick.nick} (\S+) :(.*)/ # topic findchan($1) { |c| c.topic = $2 } when /^333 #{@nick.nick} (\S+) (\S+) (\d+)/ # topic whowhen findchan($1) { |c| c.topicwhowhen = [$2, $3.to_i] } when /^352 #{@nick.nick} (\S+) (\S+) (\S+) \S+ (\S+) (\S+) :0 .*/ # Who line : 352 yournick chan user host server nick flags :0 realname chan = findchan $1 uhost = [$2, $3].join '@' nick = (findnick($4) or (@nicklist << Nick.new(self, $4, '')).last) nick.userhost = uhost chan.join nick unless nick.ison chan m = $5 m.=~ /@/ ? chan.addop(nick) : chan.delop(nick) m.=~ /%/ ? chan.addhop(nick) : chan.delhop(nick) m.=~ /\+/ ? chan.addvoice(nick) : chan.delvoice(nick) when /^353 #{@nick.nick} = (\S+) :(.*)/ # names line : 353 yournick = chan :@op1 nick1 @op2 chan = findchan $1 nicklist = $2 nicklist.split(' ').each { |n| n =~ /^([@+%]*)(.*)$/ nick = (findnick($2) or (@nicklist << Nick.new(self, $2, '')).last) chan.join nick unless nick.ison chan m = $1 m =~ /@/ ? chan.addop(nick) : chan.delop(nick) m =~ /%/ ? chan.addhop(nick) : chan.delhop(nick) m =~ /\+/ ? chan.addvoice(nick) : chan.delvoice(nick) } when /^366 #{@nick.nick} (\S+)/ # end of /NAMES chan = findchan $1 send 'WHO ' + chan.name unless chan.sync when /^376/ # end of motd (connected) @online = 1 @nick.nick = @wishnick send 'USERHOST ' + @nick.nick @handlers['online'].call when /^433 \* (\S+)/ # nick already in use @wishnick = $1 + '_' send 'NICK ' + @wishnick when /NOTICE #{@nick.nick} :(.*)/ puts ' - Server Notice : ' + $1 when /001|002|003|251|252|253|254|255|256|265|266|375|372/ # greets left alone else puts 'unh: ' + s end when /^(.+?)(!(.+?@.+))?$/ nick = (findnick($1, ($3 or '')) or Nick.new(self, $1, ($3 or ''))) if (nick == @nick) # c'est une réponse a une de nos actions case cmd when /^MODE :(.*)/ # mon mode change @nick.modmode $1 when /^MODE (\S+) (.*)/ # trigger sur mes actions ? # chan = findchan $1 # @handlers['mode'].call nick, chan, $2 when /^JOIN :(\S+)/ # i join a new chan newchan = @chanlist.find { |c| c.name == $1.downcase } unless newchan newchan = Chan.new(self, $1) @chanlist << newchan else puts ' -!- joined ' + $1 + ' but i thought i was already on :/' end @handlers['selfjoin'].call newchan when /^PART (\S+)/ # i quit a chan oldchan = findchan $1 oldchan.quit @chanlist.delete oldchan @nicklist.delete_if { |n| n.nomorechans? and n != @nick } @handlers['selfpart'].call oldchan when /^NICK :(\S+)/ oldnick = @nick.nick @nick.nick = $1 @handlers['selfnick'].call oldnick else puts 'unh: ' + s end else case cmd when /^PRIVMSG (\S+) :[\001](.*)[\001]$/ # CTCP to, ctcp = $1, $2 case ctcp when 'VERSION' send "NOTICE #{nick.nick} :\001VERSION ruby jjbot v1.0\001" when /^PING(.*)/ send "NOTICE #{nick.nick} :\001PONG#{$1}\001" else puts ' -!- unhandled ctcp ' + ctcp + ' from ' + nick.nick + ' to ' + to end when /^PRIVMSG (\S+) :(.*)$/ @handlers['privmsg'].call $1, nick, $2 when /^NOTICE (\S+) :[\001](.*)[\001]$/ # CTCP answer puts ' - received ctcp answer ' + $2 + ' to ' + $1 + ' from ' + nick.nick when /^NOTICE (\S+) :(.*)$/ @handlers['notice'].call $1, nick, $2 when /^JOIN :(\S+)/ # qqn join un chan ou on est chan = findchan $1 @nicklist << nick unless @nicklist.include? nick chan.join nick @handlers['join'].call chan, nick when /^PART (\S+)/ findchan($1) { |chan| chan.part nick @handlers['part'].call chan, nick } @nicklist.delete nick if nick.nomorechans? when /^QUIT :(.*)/ temparray = Array.new nick.chanlist.each {|c| temparray << c} temparray.each { |c| c.part nick } @nicklist.delete nick send 'NICK ' + nick.nick if nick.nick == @wishnick and @nick.nick != @wishnick @handlers['quit'].call nick when /^NICK :(\S+)/ oldnick = nick.nick nick.nick = $1 send 'NICK ' + @wishnick if oldnick == @wishnick and @nick.nick != @wishnick @handlers['nick'].call nick, oldnick when /^MODE (\S+) (.*)/ chan = findchan $1 @handlers['mode'].call chan, nick, $2 when /^INVITE (\S+) :(.*)/ if $1 == @nick.nick @handlers['invited'].call nick, $2 end when /^KICK (\S+) (\S+)/ kicked = findnick $2 chan = findchan $1 if (kicked == @nick) # i was just kicked ! chan.quit @chanlist.delete chan @nicklist.delete_if { |n| n.nomorechans? and n != @nick } @handlers['kicked'].call chan, nick else chan.part kicked @nicklist.delete nick if nick.nomorechans? @handlers['kick'].call chan, nick, kicked end else puts 'unh: ' + s end end else case cmd when /^PING (\S.*)/ send 'PONG ' + $1 when /^ERROR/ if @quitting reset else send 'PING :connected' end else puts 'unh: ' + s end end end private :handle, :findnick, :findchan end