Changeset 14747

Show
Ignore:
Timestamp:
06/28/08 11:41:22 (5 years ago)
Author:
cho45
Message:

Seperate class to each file.

Location:
lang/ruby/net-irc/trunk/lib/net
Files:
6 added
1 modified

Legend:

Unmodified
Added
Removed
  • lang/ruby/net-irc/trunk/lib/net/irc.rb

    r14733 r14747  
    1313        class IRCException < StandardError; end 
    1414 
    15         module PATTERN # :nodoc: 
    16                 # letter     =  %x41-5A / %x61-7A       ; A-Z / a-z 
    17                 # digit      =  %x30-39                 ; 0-9 
    18                 # hexdigit   =  digit / "A" / "B" / "C" / "D" / "E" / "F" 
    19                 # special    =  %x5B-60 / %x7B-7D 
    20                 #                  ; "[", "]", "\", "`", "_", "^", "{", "|", "}" 
    21                 LETTER   = 'A-Za-z' 
    22                 DIGIT    = '\d' 
    23                 HEXDIGIT = "#{DIGIT}A-Fa-f" 
    24                 SPECIAL  = '\x5B-\x60\x7B-\x7D' 
    25  
    26                 # shortname  =  ( letter / digit ) *( letter / digit / "-" ) 
    27                 #               *( letter / digit ) 
    28                 #                 ; as specified in RFC 1123 [HNAME] 
    29                 # hostname   =  shortname *( "." shortname ) 
    30                 SHORTNAME = "[#{LETTER}#{DIGIT}](?:[-#{LETTER}#{DIGIT}]*[#{LETTER}#{DIGIT}])?" 
    31                 HOSTNAME  = "#{SHORTNAME}(?:\\.#{SHORTNAME})*" 
    32  
    33                 # servername =  hostname 
    34                 SERVERNAME = HOSTNAME 
    35  
    36                 # nickname   =  ( letter / special ) *8( letter / digit / special / "-" ) 
    37                 #NICKNAME = "[#{LETTER}#{SPECIAL}\\w][-#{LETTER}#{DIGIT}#{SPECIAL}]*" 
    38                 NICKNAME = "\\S+" # for multibytes 
    39  
    40                 # user       =  1*( %x01-09 / %x0B-0C / %x0E-1F / %x21-3F / %x41-FF ) 
    41                 #                 ; any octet except NUL, CR, LF, " " and "@" 
    42                 USER = '[\x01-\x09\x0B-\x0C\x0E-\x1F\x21-\x3F\x41-\xFF]+' 
    43  
    44                 # ip4addr    =  1*3digit "." 1*3digit "." 1*3digit "." 1*3digit 
    45                 IP4ADDR = "[#{DIGIT}]{1,3}(?:\\.[#{DIGIT}]{1,3}){3}" 
    46                 # ip6addr    =  1*hexdigit 7( ":" 1*hexdigit ) 
    47                 # ip6addr    =/ "0:0:0:0:0:" ( "0" / "FFFF" ) ":" ip4addr 
    48                 IP6ADDR = "(?:[#{HEXDIGIT}]+(?::[#{HEXDIGIT}]+){7}|0:0:0:0:0:(?:0|FFFF):#{IP4ADDR})" 
    49                 # hostaddr   =  ip4addr / ip6addr 
    50                 HOSTADDR = "(?:#{IP4ADDR}|#{IP6ADDR})" 
    51  
    52                 # host       =  hostname / hostaddr 
    53                 HOST = "(?:#{HOSTNAME}|#{HOSTADDR})" 
    54  
    55                 # prefix     =  servername / ( nickname [ [ "!" user ] "@" host ] ) 
    56                 PREFIX = "(?:#{NICKNAME}(?:(?:!#{USER})?@#{HOST})?|#{SERVERNAME})" 
    57  
    58                 # nospcrlfcl =  %x01-09 / %x0B-0C / %x0E-1F / %x21-39 / %x3B-FF 
    59                 #                 ; any octet except NUL, CR, LF, " " and ":" 
    60                 NOSPCRLFCL = '\x01-\x09\x0B-\x0C\x0E-\x1F\x21-\x39\x3B-\xFF' 
    61  
    62                 # command    =  1*letter / 3digit 
    63                 COMMAND = "(?:[#{LETTER}]+|[#{DIGIT}]{3})" 
    64  
    65                 # SPACE      =  %x20        ; space character 
    66                 # middle     =  nospcrlfcl *( ":" / nospcrlfcl ) 
    67                 # trailing   =  *( ":" / " " / nospcrlfcl ) 
    68                 # params     =  *14( SPACE middle ) [ SPACE ":" trailing ] 
    69                 #            =/ 14( SPACE middle ) [ SPACE [ ":" ] trailing ] 
    70                 MIDDLE = "[#{NOSPCRLFCL}][:#{NOSPCRLFCL}]*" 
    71                 TRAILING = "[: #{NOSPCRLFCL}]*" 
    72                 PARAMS = "(?:((?: #{MIDDLE}){0,14})(?: :(#{TRAILING}))?|((?: #{MIDDLE}){14})(?::?)?(#{TRAILING}))" 
    73  
    74                 # crlf       =  %x0D %x0A   ; "carriage return" "linefeed" 
    75                 # message    =  [ ":" prefix SPACE ] command [ params ] crlf 
    76                 CRLF = '\x0D\x0A' 
    77                 MESSAGE = "(?::(#{PREFIX}) )?(#{COMMAND})#{PARAMS}\s*#{CRLF}" 
    78  
    79                 CLIENT_PATTERN  = /\A#{NICKNAME}(?:(?:!#{USER})?@#{HOST})\z/on 
    80                 MESSAGE_PATTERN = /\A#{MESSAGE}\z/on 
    81         end # PATTERN 
    82  
    83         module Constants # :nodoc: 
    84                 RPL_WELCOME           = '001' 
    85                 RPL_YOURHOST          = '002' 
    86                 RPL_CREATED           = '003' 
    87                 RPL_MYINFO            = '004' 
    88                 RPL_BOUNCE            = '005' 
    89                 RPL_USERHOST          = '302' 
    90                 RPL_ISON              = '303' 
    91                 RPL_AWAY              = '301' 
    92                 RPL_UNAWAY            = '305' 
    93                 RPL_NOWAWAY           = '306' 
    94                 RPL_WHOISUSER         = '311' 
    95                 RPL_WHOISSERVER       = '312' 
    96                 RPL_WHOISOPERATOR     = '313' 
    97                 RPL_WHOISIDLE         = '317' 
    98                 RPL_ENDOFWHOIS        = '318' 
    99                 RPL_WHOISCHANNELS     = '319' 
    100                 RPL_WHOWASUSER        = '314' 
    101                 RPL_ENDOFWHOWAS       = '369' 
    102                 RPL_LISTSTART         = '321' 
    103                 RPL_LIST              = '322' 
    104                 RPL_LISTEND           = '323' 
    105                 RPL_UNIQOPIS          = '325' 
    106                 RPL_CHANNELMODEIS     = '324' 
    107                 RPL_NOTOPIC           = '331' 
    108                 RPL_TOPIC             = '332' 
    109                 RPL_INVITING          = '341' 
    110                 RPL_SUMMONING         = '342' 
    111                 RPL_INVITELIST        = '346' 
    112                 RPL_ENDOFINVITELIST   = '347' 
    113                 RPL_EXCEPTLIST        = '348' 
    114                 RPL_ENDOFEXCEPTLIST   = '349' 
    115                 RPL_VERSION           = '351' 
    116                 RPL_WHOREPLY          = '352' 
    117                 RPL_ENDOFWHO          = '315' 
    118                 RPL_NAMREPLY          = '353' 
    119                 RPL_ENDOFNAMES        = '366' 
    120                 RPL_LINKS             = '364' 
    121                 RPL_ENDOFLINKS        = '365' 
    122                 RPL_BANLIST           = '367' 
    123                 RPL_ENDOFBANLIST      = '368' 
    124                 RPL_INFO              = '371' 
    125                 RPL_ENDOFINFO         = '374' 
    126                 RPL_MOTDSTART         = '375' 
    127                 RPL_MOTD              = '372' 
    128                 RPL_ENDOFMOTD         = '376' 
    129                 RPL_YOUREOPER         = '381' 
    130                 RPL_REHASHING         = '382' 
    131                 RPL_YOURESERVICE      = '383' 
    132                 RPL_TIME              = '391' 
    133                 RPL_USERSSTART        = '392' 
    134                 RPL_USERS             = '393' 
    135                 RPL_ENDOFUSERS        = '394' 
    136                 RPL_NOUSERS           = '395' 
    137                 RPL_TRACELINK         = '200' 
    138                 RPL_TRACECONNECTING   = '201' 
    139                 RPL_TRACEHANDSHAKE    = '202' 
    140                 RPL_TRACEUNKNOWN      = '203' 
    141                 RPL_TRACEOPERATOR     = '204' 
    142                 RPL_TRACEUSER         = '205' 
    143                 RPL_TRACESERVER       = '206' 
    144                 RPL_TRACESERVICE      = '207' 
    145                 RPL_TRACENEWTYPE      = '208' 
    146                 RPL_TRACECLASS        = '209' 
    147                 RPL_TRACERECONNECT    = '210' 
    148                 RPL_TRACELOG          = '261' 
    149                 RPL_TRACEEND          = '262' 
    150                 RPL_STATSLINKINFO     = '211' 
    151                 RPL_STATSCOMMANDS     = '212' 
    152                 RPL_ENDOFSTATS        = '219' 
    153                 RPL_STATSUPTIME       = '242' 
    154                 RPL_STATSOLINE        = '243' 
    155                 RPL_UMODEIS           = '221' 
    156                 RPL_SERVLIST          = '234' 
    157                 RPL_SERVLISTEND       = '235' 
    158                 RPL_LUSERCLIENT       = '251' 
    159                 RPL_LUSEROP           = '252' 
    160                 RPL_LUSERUNKNOWN      = '253' 
    161                 RPL_LUSERCHANNELS     = '254' 
    162                 RPL_LUSERME           = '255' 
    163                 RPL_ADMINME           = '256' 
    164                 RPL_ADMINLOC1         = '257' 
    165                 RPL_ADMINLOC2         = '258' 
    166                 RPL_ADMINEMAIL        = '259' 
    167                 RPL_TRYAGAIN          = '263' 
    168                 ERR_NOSUCHNICK        = '401' 
    169                 ERR_NOSUCHSERVER      = '402' 
    170                 ERR_NOSUCHCHANNEL     = '403' 
    171                 ERR_CANNOTSENDTOCHAN  = '404' 
    172                 ERR_TOOMANYCHANNELS   = '405' 
    173                 ERR_WASNOSUCHNICK     = '406' 
    174                 ERR_TOOMANYTARGETS    = '407' 
    175                 ERR_NOSUCHSERVICE     = '408' 
    176                 ERR_NOORIGIN          = '409' 
    177                 ERR_NORECIPIENT       = '411' 
    178                 ERR_NOTEXTTOSEND      = '412' 
    179                 ERR_NOTOPLEVEL        = '413' 
    180                 ERR_WILDTOPLEVEL      = '414' 
    181                 ERR_BADMASK           = '415' 
    182                 ERR_UNKNOWNCOMMAND    = '421' 
    183                 ERR_NOMOTD            = '422' 
    184                 ERR_NOADMININFO       = '423' 
    185                 ERR_FILEERROR         = '424' 
    186                 ERR_NONICKNAMEGIVEN   = '431' 
    187                 ERR_ERRONEUSNICKNAME  = '432' 
    188                 ERR_NICKNAMEINUSE     = '433' 
    189                 ERR_NICKCOLLISION     = '436' 
    190                 ERR_UNAVAILRESOURCE   = '437' 
    191                 ERR_USERNOTINCHANNEL  = '441' 
    192                 ERR_NOTONCHANNEL      = '442' 
    193                 ERR_USERONCHANNEL     = '443' 
    194                 ERR_NOLOGIN           = '444' 
    195                 ERR_SUMMONDISABLED    = '445' 
    196                 ERR_USERSDISABLED     = '446' 
    197                 ERR_NOTREGISTERED     = '451' 
    198                 ERR_NEEDMOREPARAMS    = '461' 
    199                 ERR_ALREADYREGISTRED  = '462' 
    200                 ERR_NOPERMFORHOST     = '463' 
    201                 ERR_PASSWDMISMATCH    = '464' 
    202                 ERR_YOUREBANNEDCREEP  = '465' 
    203                 ERR_YOUWILLBEBANNED   = '466' 
    204                 ERR_KEYSET            = '467' 
    205                 ERR_CHANNELISFULL     = '471' 
    206                 ERR_UNKNOWNMODE       = '472' 
    207                 ERR_INVITEONLYCHAN    = '473' 
    208                 ERR_BANNEDFROMCHAN    = '474' 
    209                 ERR_BADCHANNELKEY     = '475' 
    210                 ERR_BADCHANMASK       = '476' 
    211                 ERR_NOCHANMODES       = '477' 
    212                 ERR_BANLISTFULL       = '478' 
    213                 ERR_NOPRIVILEGES      = '481' 
    214                 ERR_CHANOPRIVSNEEDED  = '482' 
    215                 ERR_CANTKILLSERVER    = '483' 
    216                 ERR_RESTRICTED        = '484' 
    217                 ERR_UNIQOPPRIVSNEEDED = '485' 
    218                 ERR_NOOPERHOST        = '491' 
    219                 ERR_UMODEUNKNOWNFLAG  = '501' 
    220                 ERR_USERSDONTMATCH    = '502' 
    221                 RPL_SERVICEINFO       = '231' 
    222                 RPL_ENDOFSERVICES     = '232' 
    223                 RPL_SERVICE           = '233' 
    224                 RPL_NONE              = '300' 
    225                 RPL_WHOISCHANOP       = '316' 
    226                 RPL_KILLDONE          = '361' 
    227                 RPL_CLOSING           = '362' 
    228                 RPL_CLOSEEND          = '363' 
    229                 RPL_INFOSTART         = '373' 
    230                 RPL_MYPORTIS          = '384' 
    231                 RPL_STATSCLINE        = '213' 
    232                 RPL_STATSNLINE        = '214' 
    233                 RPL_STATSILINE        = '215' 
    234                 RPL_STATSKLINE        = '216' 
    235                 RPL_STATSQLINE        = '217' 
    236                 RPL_STATSYLINE        = '218' 
    237                 RPL_STATSVLINE        = '240' 
    238                 RPL_STATSLLINE        = '241' 
    239                 RPL_STATSHLINE        = '244' 
    240                 RPL_STATSSLINE        = '244' 
    241                 RPL_STATSPING         = '246' 
    242                 RPL_STATSBLINE        = '247' 
    243                 RPL_STATSDLINE        = '250' 
    244                 ERR_NOSERVICEHOST     = '492' 
    245  
    246                 PASS     = 'PASS' 
    247                 NICK     = 'NICK' 
    248                 USER     = 'USER' 
    249                 OPER     = 'OPER' 
    250                 MODE     = 'MODE' 
    251                 SERVICE  = 'SERVICE' 
    252                 QUIT     = 'QUIT' 
    253                 SQUIT    = 'SQUIT' 
    254                 JOIN     = 'JOIN' 
    255                 PART     = 'PART' 
    256                 TOPIC    = 'TOPIC' 
    257                 NAMES    = 'NAMES' 
    258                 LIST     = 'LIST' 
    259                 INVITE   = 'INVITE' 
    260                 KICK     = 'KICK' 
    261                 PRIVMSG  = 'PRIVMSG' 
    262                 NOTICE   = 'NOTICE' 
    263                 MOTD     = 'MOTD' 
    264                 LUSERS   = 'LUSERS' 
    265                 VERSION  = 'VERSION' 
    266                 STATS    = 'STATS' 
    267                 LINKS    = 'LINKS' 
    268                 TIME     = 'TIME' 
    269                 CONNECT  = 'CONNECT' 
    270                 TRACE    = 'TRACE' 
    271                 ADMIN    = 'ADMIN' 
    272                 INFO     = 'INFO' 
    273                 SERVLIST = 'SERVLIST' 
    274                 SQUERY   = 'SQUERY' 
    275                 WHO      = 'WHO' 
    276                 WHOIS    = 'WHOIS' 
    277                 WHOWAS   = 'WHOWAS' 
    278                 KILL     = 'KILL' 
    279                 PING     = 'PING' 
    280                 PONG     = 'PONG' 
    281                 ERROR    = 'ERROR' 
    282                 AWAY     = 'AWAY' 
    283                 REHASH   = 'REHASH' 
    284                 DIE      = 'DIE' 
    285                 RESTART  = 'RESTART' 
    286                 SUMMON   = 'SUMMON' 
    287                 USERS    = 'USERS' 
    288                 WALLOPS  = 'WALLOPS' 
    289                 USERHOST = 'USERHOST' 
    290                 ISON     = 'ISON' 
    291         end 
    292  
    293         COMMANDS = Constants.constants.inject({}) {|r,i| # :nodoc: 
    294                 r.update(Constants.const_get(i) => i) 
    295         } 
     15        autoload :PATTERN,   "net/irc/pattern" 
     16        autoload :Constants, "net/irc/constants" 
     17        autoload :COMMANDS,  "net/irc/constants" 
     18        autoload :Message,   "net/irc/message" 
     19        autoload :Client,    "net/irc/client" 
     20        autoload :Server,    "net/irc/server" 
    29621 
    29722        class Prefix < String 
     
    33358end 
    33459 
    335 class Net::IRC::Message 
    336         include Net::IRC 
    337  
    338         class InvalidMessage < Net::IRC::IRCException; end 
    339  
    340         attr_reader :prefix, :command, :params 
    341  
    342         # Parse string and return new Message. 
    343         # If the string is invalid message, this method raises Net::IRC::Message::InvalidMessage. 
    344         def self.parse(str) 
    345                 _, prefix, command, *rest = *PATTERN::MESSAGE_PATTERN.match(str) 
    346                 raise InvalidMessage, "Invalid message: #{str.dump}" unless _ 
    347  
    348                 case 
    349                 when rest[0] && !rest[0].empty? 
    350                         middle, trailer, = *rest 
    351                 when rest[2] && !rest[2].empty? 
    352                         middle, trailer, = *rest[2, 2] 
    353                 when rest[1] 
    354                         params  = [] 
    355                         trailer = rest[1] 
    356                 when rest[3] 
    357                         params  = [] 
    358                         trailer = rest[3] 
    359                 else 
    360                         params  = [] 
    361                 end 
    362  
    363                 params ||= middle.split(/ /)[1..-1] 
    364                 params << trailer if trailer 
    365  
    366                 new(prefix, command, params) 
    367         end 
    368  
    369         def initialize(prefix, command, params) 
    370                 @prefix  = Prefix.new(prefix.to_s) 
    371                 @command = command 
    372                 @params  = params 
    373         end 
    374  
    375         # Same as @params[n]. 
    376         def [](n) 
    377                 @params[n] 
    378         end 
    379  
    380         # Iterate params. 
    381         def each(&block) 
    382                 @params.each(&block) 
    383         end 
    384  
    385         # Stringfy message to raw IRC message. 
    386         def to_s 
    387                 str = "" 
    388  
    389                 str << ":#{@prefix} " unless @prefix.empty? 
    390                 str << @command 
    391  
    392                 if @params 
    393                         f = false 
    394                         @params.each do |param| 
    395                                 str << " " 
    396                                 if !f && (param.size == 0 || / / =~ param || /^:/ =~ param) 
    397                                         str << ":#{param}" 
    398                                         f = true 
    399                                 else 
    400                                         str << param 
    401                                 end 
    402                         end 
    403                 end 
    404  
    405                 str << "\x0D\x0A" 
    406  
    407                 str 
    408         end 
    409         alias to_str to_s 
    410  
    411         # Same as params. 
    412         def to_a 
    413                 @params 
    414         end 
    415  
    416         # If the message is CTCP, return true. 
    417         def ctcp? 
    418                 message = @params[1] 
    419                 message[0] == 1 && message[message.length-1] == 1 
    420         end 
    421  
    422         def inspect 
    423                 '#<%s:0x%x prefix:%s command:%s params:%s>' % [ 
    424                         self.class, 
    425                         self.object_id, 
    426                         @prefix, 
    427                         @command, 
    428                         @params.inspect 
    429                 ] 
    430         end 
    431  
    432         class ModeParser 
    433  
    434                 def initialize(require_arg, definition) 
    435                         @require_arg = require_arg.map {|i| i.to_sym } 
    436                         @definition  = definition 
    437                 end 
    438  
    439                 def parse(arg) 
    440                         params = arg.kind_of?(Net::IRC::Message) ? arg.to_a : arg.split(/\s+/) 
    441  
    442                         ret =  { 
    443                                 :positive => [], 
    444                                 :negative => [], 
    445                         } 
    446  
    447                         current = nil, arg_pos = 0 
    448                         params[1].each_byte do |c| 
    449                                 sym = c.chr.to_sym 
    450                                 case sym 
    451                                 when :+ 
    452                                         current = ret[:positive] 
    453                                 when :- 
    454                                         current = ret[:negative] 
    455                                 else 
    456                                         case 
    457                                         when @require_arg.include?(sym) 
    458                                                 current << [sym, params[arg_pos + 2]] 
    459                                                 arg_pos += 1 
    460                                         when @definition.key?(sym) 
    461                                                 current << [sym, nil] 
    462                                         else 
    463                                                 # fallback, should raise exception 
    464                                                 # but not for convenience 
    465                                                 current << [sym, nil] 
    466                                         end 
    467                                 end 
    468                         end 
    469  
    470                         ret 
    471                 end 
    472  
    473                 module RFC1459 
    474                         Channel  = ModeParser.new(%w|o l b v k|, { 
    475                                 :o => "give/take channel operator privileges", 
    476                                 :p => "private channel flag", 
    477                                 :s => "select channel flag", 
    478                                 :i => "invite-only channel flag", 
    479                                 :t => "topic settable by channel operator only flag", 
    480                                 :n => "no messages to channel from clients on the outside", 
    481                                 :m => "moderated channel", 
    482                                 :l => "set the user limit to channel", 
    483                                 :b => "set a ban mask to keep users out", 
    484                                 :v => "give/take the ability to speak on a moderated channel", 
    485                                 :k => "set a channel key (password)", 
    486                         }) 
    487                         User    = ModeParser.new(%w||, { 
    488                                 :i => "marks a users as invisible", 
    489                                 :s => "marks a user for receipt of server notices", 
    490                                 :w => "user receives wallops", 
    491                                 :o => "operator flag", 
    492                         }) 
    493                 end 
    494         end 
    495 end # Message 
    496  
    497 class Net::IRC::Client 
    498         include Net::IRC 
    499         include Constants 
    500  
    501         attr_reader :host, :port, :opts 
    502         attr_reader :prefix, :channels 
    503  
    504         def initialize(host, port, opts={}) 
    505                 @host = host 
    506                 @port = port 
    507                 @opts = OpenStruct.new(opts) 
    508                 @log  = @opts.logger || Logger.new($stdout) 
    509                 @channels = { 
    510 #                       "#channel" => { 
    511 #                               :modes => [], 
    512 #                               :users => [], 
    513 #                       } 
    514                 } 
    515                 @channels.extend(MonitorMixin) 
    516         end 
    517  
    518         # Connect to server and start loop. 
    519         def start 
    520                 @socket = TCPSocket.open(@host, @port) 
    521                 on_connected 
    522                 post PASS, @opts.pass if @opts.pass 
    523                 post NICK, @opts.nick 
    524                 post USER, @opts.user, "0", "*", @opts.real 
    525                 while l = @socket.gets 
    526                         begin 
    527                                 @log.debug "RECEIVE: #{l.chomp}" 
    528                                 m = Message.parse(l) 
    529                                 next if on_message(m) === true 
    530                                 name = "on_#{(COMMANDS[m.command.upcase] || m.command).downcase}" 
    531                                 send(name, m) if respond_to?(name) 
    532                         rescue Exception => e 
    533                                 warn e 
    534                                 warn e.backtrace.join("\r\t") 
    535                                 raise 
    536                         rescue Message::InvalidMessage 
    537                                 @log.error "MessageParse: " + l.inspect 
    538                         end 
    539                 end 
    540         rescue IOError 
    541         ensure 
    542                 finish 
    543         end 
    544  
    545         # Close connection to server. 
    546         def finish 
    547                 begin 
    548                         @socket.close 
    549                 rescue 
    550                 end 
    551                 on_disconnected 
    552         end 
    553  
    554         # Catch all messages. 
    555         # If this method return true, aother callback will not be called. 
    556         def on_message(m) 
    557         end 
    558  
    559         # Default RPL_WELCOME callback. 
    560         # This sets @prefix from the message. 
    561         def on_rpl_welcome(m) 
    562                 @prefix = Prefix.new(m[1][/\S+$/]) 
    563         end 
    564  
    565         # Default PING callback. Response PONG. 
    566         def on_ping(m) 
    567                 post PONG, @prefix ? @prefix.nick : "" 
    568         end 
    569  
    570         # For managing channel 
    571         def on_rpl_namreply(m) 
    572                 type    = m[1] 
    573                 channel = m[2] 
    574                 init_channel(channel) 
    575  
    576                 @channels.synchronize do 
    577                         m[3].split(/\s+/).each do |u| 
    578                                 _, mode, nick = *u.match(/^([@+]?)(.+)/) 
    579  
    580                                 @channels[channel][:users] << nick 
    581                                 @channels[channel][:users].uniq! 
    582  
    583                                 case mode 
    584                                 when "@" # channel operator 
    585                                         @channels[channel][:modes] << [:o, nick] 
    586                                 when "+" # voiced (under moderating mode) 
    587                                         @channels[channel][:modes] << [:v, nick] 
    588                                 end 
    589                         end 
    590  
    591                         case type 
    592                         when "@" # secret 
    593                                 @channels[channel][:modes] << [:s, nil] 
    594                         when "*" # private 
    595                                 @channels[channel][:modes] << [:p, nil] 
    596                         when "=" # public 
    597                         end 
    598  
    599                         @channels[channel][:modes].uniq! 
    600                 end 
    601         end 
    602  
    603         # For managing channel 
    604         def on_part(m) 
    605                 nick    = m.prefix.nick 
    606                 channel = m[0] 
    607                 init_channel(channel) 
    608  
    609                 @channels.synchronize do 
    610                         info = @channels[channel] 
    611                         if info 
    612                                 info[:users].delete(nick) 
    613                                 info[:modes].delete_if {|u| 
    614                                         u[1] == nick 
    615                                 } 
    616                         end 
    617                 end 
    618         end 
    619  
    620         # For managing channel 
    621         def on_quit(m) 
    622                 nick = m.prefix.nick 
    623  
    624                 @channels.synchronize do 
    625                         @channels.each do |channel, info| 
    626                                 info[:users].delete(nick) 
    627                                 info[:modes].delete_if {|u| 
    628                                         u[1] == nick 
    629                                 } 
    630                         end 
    631                 end 
    632         end 
    633  
    634         # For managing channel 
    635         def on_kick(m) 
    636                 users = m[1].split(/,/) 
    637  
    638                 @channels.synchronize do 
    639                         m[0].split(/,/).each do |chan| 
    640                                 init_channel(chan) 
    641                                 info = @channels[chan] 
    642                                 if info 
    643                                         users.each do |nick| 
    644                                                 info[:users].delete(nick) 
    645                                                 info[:modes].delete_if {|u| 
    646                                                         u[1] == nick 
    647                                                 } 
    648                                         end 
    649                                 end 
    650                         end 
    651                 end 
    652         end 
    653  
    654         # For managing channel 
    655         def on_join(m) 
    656                 nick    = m.prefix.nick 
    657                 channel = m[0] 
    658  
    659                 @channels.synchronize do 
    660                         init_channel(channel) 
    661  
    662                         @channels[channel][:users] << nick 
    663                         @channels[channel][:users].uniq! 
    664                 end 
    665         end 
    666  
    667         # For managing channel 
    668         def on_mode(m) 
    669                 channel = m[0] 
    670                 @channels.synchronize do 
    671                         init_channel(channel) 
    672  
    673                         mode = Message::ModeParser::RFC1459::Channel.parse(m) 
    674                         mode[:negative].each do |m| 
    675                                 @channels[channel][:modes].delete(m) 
    676                         end 
    677  
    678                         mode[:positive].each do |m| 
    679                                 @channels[channel][:modes] << m 
    680                         end 
    681  
    682                         @channels[channel][:modes].uniq! 
    683                         [mode[:negative], mode[:positive]] 
    684                 end 
    685         end 
    686  
    687         # For managing channel 
    688         def init_channel(channel) 
    689                 @channels[channel] ||= { 
    690                         :modes => [], 
    691                         :users => [], 
    692                 } 
    693         end 
    694  
    695         # Do nothing. 
    696         # This is for avoiding error on calling super. 
    697         # So you can always call super at subclass. 
    698         def method_missing(name, *args) 
    699         end 
    700  
    701         # Call when socket connected. 
    702         def on_connected 
    703         end 
    704  
    705         # Call when socket closed. 
    706         def on_disconnected 
    707         end 
    708  
    709         private 
    710  
    711         # Post message to server. 
    712         # 
    713         #     include Net::IRC::Constants 
    714         #     post PRIVMSG, "#channel", "foobar" 
    715         def post(command, *params) 
    716                 m = Message.new(nil, command, params.map {|s| 
    717                         s ? s.gsub(/[\r\n]/, " ") : "" 
    718                 }) 
    719  
    720                 @log.debug "SEND: #{m.to_s.chomp}" 
    721                 @socket << m 
    722         end 
    723 end # Client 
    724  
    725 class Net::IRC::Server 
    726         # Server global state for accessing Server::Session 
    727         attr_accessor :state 
    728  
    729         def initialize(host, port, session_class, opts={}) 
    730                 @host          = host 
    731                 @port          = port 
    732                 @session_class = session_class 
    733                 @opts          = OpenStruct.new(opts) 
    734                 @sessions      = [] 
    735                 @state         = {} 
    736         end 
    737  
    738         # Start server loop. 
    739         def start 
    740                 @serv = TCPServer.new(@host, @port) 
    741                 @log  = @opts.logger || Logger.new($stdout) 
    742                 @log.info "Host: #{@host} Port:#{@port}" 
    743                 @accept = Thread.start do 
    744                         loop do 
    745                                 Thread.start(@serv.accept) do |s| 
    746                                         begin 
    747                                                 @log.info "Client connected, new session starting..." 
    748                                                 s = @session_class.new(self, s, @log, @opts) 
    749                                                 @sessions << s 
    750                                                 s.start 
    751                                         rescue Exception => e 
    752                                                 puts e 
    753                                                 puts e.backtrace 
    754                                         ensure 
    755                                                 @sessions.delete(s) 
    756                                         end 
    757                                 end 
    758                         end 
    759                 end 
    760                 @accept.join 
    761         end 
    762  
    763         # Close all sessions. 
    764         def finish 
    765                 Thread.exclusive do 
    766                         @accept.kill 
    767                         begin 
    768                                 @serv.close 
    769                         rescue 
    770                         end 
    771                         @sessions.each do |s| 
    772                                 s.finish 
    773                         end 
    774                 end 
    775         end 
    776  
    777  
    778         class Session 
    779                 include Net::IRC 
    780                 include Constants 
    781  
    782                 attr_reader :prefix, :nick, :real, :host 
    783  
    784                 # Override subclass. 
    785                 def server_name 
    786                         "net-irc" 
    787                 end 
    788  
    789                 # Override subclass. 
    790                 def server_version 
    791                         "0.0.0" 
    792                 end 
    793  
    794                 # Override subclass. 
    795                 def avaiable_user_modes 
    796                         "eixwy" 
    797                 end 
    798  
    799                 # Override subclass. 
    800                 def avaiable_channel_modes 
    801                         "spknm" 
    802                 end 
    803  
    804                 def initialize(server, socket, logger, opts={}) 
    805                         @server, @socket, @log, @opts = server, socket, logger, opts 
    806                 end 
    807  
    808                 def self.start(*args) 
    809                         new(*args).start 
    810                 end 
    811  
    812                 # Start session loop. 
    813                 def start 
    814                         on_connected 
    815                         while l = @socket.gets 
    816                                 begin 
    817                                         @log.debug "RECEIVE: #{l.chomp}" 
    818                                         m = Message.parse(l) 
    819                                         next if on_message(m) === true 
    820  
    821                                         name = "on_#{(COMMANDS[m.command.upcase] || m.command).downcase}" 
    822                                         send(name, m) if respond_to?(name) 
    823  
    824                                         break if m.command == QUIT 
    825                                 rescue Message::InvalidMessage 
    826                                         @log.error "MessageParse: " + l.inspect 
    827                                 end 
    828                         end 
    829                 rescue IOError 
    830                 ensure 
    831                         finish 
    832                 end 
    833  
    834                 # Close this session. 
    835                 def finish 
    836                         begin 
    837                                 @socket.close 
    838                         rescue 
    839                         end 
    840                         on_disconnected 
    841                 end 
    842  
    843                 # Default PASS callback. 
    844                 # Set @pass. 
    845                 def on_pass(m) 
    846                         @pass = m.params[0] 
    847                 end 
    848  
    849                 # Default NICK callback. 
    850                 # Set @nick. 
    851                 def on_nick(m) 
    852                         @nick = m.params[0] 
    853                 end 
    854  
    855                 # Default USER callback. 
    856                 # Set @user, @real, @host and call initial_message. 
    857                 def on_user(m) 
    858                         @user, @real = m.params[0], m.params[3] 
    859                         @host = @socket.peeraddr[2] 
    860                         @prefix = Prefix.new("#{@nick}!#{@user}@#{@host}") 
    861                         initial_message 
    862                 end 
    863  
    864                 # Call when socket connected. 
    865                 def on_connected 
    866                 end 
    867  
    868                 # Call when socket closed. 
    869                 def on_disconnected 
    870                 end 
    871  
    872                 # Catch all messages. 
    873                 # If this method return true, aother callback will not be called. 
    874                 def on_message(m) 
    875                 end 
    876  
    877                 # Default PING callback. Response PONG. 
    878                 def on_ping(m) 
    879                         post server_name, PONG, m.params[0] 
    880                 end 
    881  
    882                 # Do nothing. 
    883                 # This is for avoiding error on calling super. 
    884                 # So you can always call super at subclass. 
    885                 def method_missing(name, *args) 
    886                 end 
    887  
    888                 private 
    889                 # Post message to server. 
    890                 # 
    891                 #     include Net::IRC::Constans 
    892                 #     post prefix, PRIVMSG, "#channel", "foobar" 
    893                 def post(prefix, command, *params) 
    894                         m = Message.new(prefix, command, params.map {|s| 
    895                                 s.gsub(/[\r\n]/, " ") 
    896                         }) 
    897                         @log.debug "SEND: #{m.to_s.chomp}" 
    898                         @socket << m 
    899                 rescue IOError 
    900                         finish 
    901                 end 
    902  
    903                 # Call when client connected. 
    904                 # Send RPL_WELCOME sequence. If you want to customize, override this method at subclass. 
    905                 def initial_message 
    906                         post server_name, RPL_WELCOME,  @nick, "Welcome to the Internet Relay Network #{@prefix}" 
    907                         post server_name, RPL_YOURHOST, @nick, "Your host is #{server_name}, running version #{server_version}" 
    908                         post server_name, RPL_CREATED,  @nick, "This server was created #{Time.now}" 
    909                         post server_name, RPL_MYINFO,   @nick, "#{server_name} #{server_version} #{avaiable_user_modes} #{avaiable_channel_modes}" 
    910                 end 
    911         end 
    912 end # Server