root/lang/ruby/chokan/trunk/rice/irc.rb @ 1391

Revision 1391, 14.9 kB (checked in by yoko, 6 years ago)

lang/ruby/chokan/trunk/rice/irc.rb: 判定修正

Line 
1=begin
2
3= rice - Ruby Irc interfaCE
4
5  $Id: irc.rb,v 1.9 2001/06/13 10:22:24 akira Exp $
6
7  Copyright (c) 2001 akira yamada <akira@ruby-lang.org>
8  You can redistribute it and/or modify it under the same term as Ruby.
9
10=end
11
12require 'socket'
13require 'thread'
14require 'monitor'
15
16module RICE
17  class Error < StandardError; end
18  class InvalidMessage < Error; end
19  class UnknownCommand < Error; end
20
21=begin
22
23== RICE::Connection
24
25=end
26
27  class Connection
28    class Error < StandardError; end
29    class Closed < Error; end
30
31=begin
32
33--- RICE::Connection::new
34
35=end
36
37    def initialize(server, port, eol = "\r\n")
38      @conn = []
39      @conn.extend(MonitorMixin)
40
41      self.server = server
42      self.port   = port
43      self.eol    = eol
44
45      @read_q  = Queue.new
46
47      @read_th  = Thread.new(@read_q, @eol) do |read_q, eol|
48        read_thread(read_q, eol)
49      end
50
51      @threads = {}
52      @threads.extend(MonitorMixin)
53
54      @dispatcher = Thread.new(@read_q) do |read_q|
55        loop do
56          x = read_q.pop
57
58          ths = @threads.synchronize do
59            @threads.keys
60          end
61          ths.each do |th|
62            if th.status
63              @threads[th].q.push(x)
64            else
65              @threads.delete(th)
66            end
67          end
68        end # loop
69
70      end
71
72      @delay = 0.3
73    end
74    attr :delay, true
75
76=begin
77
78--- RICE::Connection#server=(server)
79
80=end
81
82    def server=(server)
83      raise RuntimeError,
84        "Already connected to #{@server}:#{@port}" unless @conn.empty?
85      @server = server
86    end
87
88=begin
89
90--- RICE::Connection#port=(port)
91
92=end
93
94    def port=(port)
95      raise RuntimeError,
96        "Already connected to #{@server}:#{@port}" unless @conn.empty?
97      @port = port
98    end
99
100=begin
101
102--- RICE::Connection#eol=(eol)
103
104=end
105
106    def eol=(eol)
107      raise RuntimeError,
108        "Already connected to #{@server}:#{@port}" unless @conn.empty?
109      @eol = eol
110    end
111
112=begin
113
114--- RICE::Connection#start(max_retry = 3, retry_wait = 30)
115
116=end
117
118    def start(max_retry = 3, retry_wait = 30)
119      @client_th = Thread.current # caller thread
120      if alive?
121        #sleep retry_wait
122        return nil
123      end
124
125      if block_given?
126        @main_th = Thread.new do
127          yield
128        end
129      else
130        @main_th = Thread.new do
131          begin
132            Thread.stop
133          ensure
134            @read_th.raise(Closed) if @read_th.status
135            close(true)
136            @client_th.raise(Closed)
137          end
138        end
139      end
140
141      begin
142        open_conn
143      rescue SystemCallError
144        max_retry -= 1
145        if max_retry == 0
146          raise
147        end
148        sleep retry_wait
149        retry
150      end
151
152      @main_th.join
153      nil
154    end
155
156    def open_conn
157      @conn.synchronize do
158        @conn[0] = TCPSocket.new(@server, @port)
159      end
160      @conn[0].extend(MonitorMixin)
161
162      @read_th.run
163
164      ths = @threads.synchronize do
165        @threads.keys
166      end
167      ths.each do |th|
168        th.run if th.status && th.stop?
169      end
170    end
171    private :open_conn
172
173=begin
174
175--- RICE::Connection#regist(raise_on_close, *args) {...}
176
177=end
178
179    USER_THREAD = Struct.new('User_Thread', :q, :raise_on_close)
180    def regist(raise_on_close = false, *args)
181      read_q = Queue.new
182      th = Thread.new(read_q, self, *args) do |read_q, conn, *args|
183        yield(read_q, conn, *args)
184      end
185      @threads.synchronize do
186        @threads[th] = USER_THREAD.new(read_q, raise_on_close)
187      end
188      th
189    end
190
191=begin
192
193--- RICE::Connection#unregist(thread)
194
195=end
196
197    def unregist(thread)
198      th = nil
199      @threads.synchronize do
200        th = @threads.delete(th)
201      end
202      th.exit
203      th
204    end
205
206    def read_thread(read_q, eol)
207      begin
208        read_q.clear
209        Thread.stop
210
211        begin
212          conn = @conn[0]
213          while l = conn.gets(eol)
214            begin
215              read_q.push(Message.parse(l))
216            rescue UnknownCommand
217              $stderr.print l.inspect if $DEBUG
218            rescue InvalidMessage
219              begin
220                read_q.push(Message.parse(l.sub(/\s*#{eol}\z/o, eol)))
221              rescue
222                $stderr.print l.inspect if $DEBUG
223              end
224            end
225          end
226
227        rescue IOError#, SystemCallError
228          $stderr.print "#{self.inspect}: read_th get error #{$!}" if $DEBUG
229
230        ensure
231          raise Closed
232        end
233
234      rescue Closed
235        begin
236          @main_th.run
237        rescue Closed
238        end
239        retry
240      end
241    end
242    private :read_thread
243
244=begin
245
246--- RICE::Connection#close(restart = false)
247
248=end
249
250    def close(restart = false)
251      begin
252        unless restart
253          @main_th.exit if @main_th.alive?
254          @read_th.exit if @read_th.alive?
255        end
256
257        conn = nil
258        @conn.synchronize do
259          conn = @conn.shift
260        end
261        conn.close if conn
262
263        @threads.synchronize do
264          @threads.each_key do |th|
265            if restart
266              if @threads[th].raise_on_close
267                if @threads[th].raise_on_close.kind_of?(Exception)
268                  th.raise(@threads[th].raise_on_close)
269                else
270                  th.raise(Closed)
271                end
272              end
273
274            else
275              th.exit
276            end
277          end
278        end
279
280      end
281    end
282
283=begin
284
285--- RICE::Connection#alive?
286
287=end
288
289    def alive?
290      @main_th && @main_th.alive?
291    end
292
293=begin
294
295--- RICE::Connection#push(message)
296
297=end
298
299    def push(message)
300      if @conn[0]
301        @conn[0].synchronize do
302          sleep(@delay) if @delay
303          @conn[0].print message.to_s
304        end
305      else
306        nil
307      end
308    end
309    alias << push
310  end # Connection
311
312=begin
313
314== RICE::Message
315
316=end
317
318  class Message
319    module PATTERN
320      # letter     =  %x41-5A / %x61-7A       ; A-Z / a-z
321      # digit      =  %x30-39                 ; 0-9
322      # hexdigit   =  digit / "A" / "B" / "C" / "D" / "E" / "F"
323      # special    =  %x5B-60 / %x7B-7D
324      #                  ; "[", "]", "\", "`", "_", "^", "{", "|", "}"
325      LETTER   = 'A-Za-z'
326      DIGIT    = '\d'
327      HEXDIGIT = "#{DIGIT}A-Fa-f"
328      SPECIAL  = '\x5B-\x60\x7B-\x7D'
329
330      # shortname  =  ( letter / digit ) *( letter / digit / "-" )
331      #               *( letter / digit )
332      #                 ; as specified in RFC 1123 [HNAME]
333      # hostname   =  shortname *( "." shortname )
334      SHORTNAME = "[#{LETTER}#{DIGIT}](?:[-#{LETTER}#{DIGIT}]*[#{LETTER}#{DIGIT}])?"
335      HOSTNAME  = "#{SHORTNAME}(?:\\.#{SHORTNAME})*"
336
337      # servername =  hostname
338      SERVERNAME = HOSTNAME
339
340      # nickname   =  ( letter / special ) *8( letter / digit / special / "-" )
341      NICKNAME = "[#{LETTER}#{SPECIAL}][-#{LETTER}#{DIGIT}#{SPECIAL}]{0,8}"
342
343      # user       =  1*( %x01-09 / %x0B-0C / %x0E-1F / %x21-3F / %x41-FF )
344      #                 ; any octet except NUL, CR, LF, " " and "@"
345      USER = '[\x01-\x09\x0B-\x0C\x0E-\x1F\x21-\x3F\x41-\xFF]+'
346
347      # ip4addr    =  1*3digit "." 1*3digit "." 1*3digit "." 1*3digit
348      IP4ADDR = "[#{DIGIT}]{1,3}(?:\\.[#{DIGIT}]{1,3}){3}"
349      # ip6addr    =  1*hexdigit 7( ":" 1*hexdigit )
350      # ip6addr    =/ "0:0:0:0:0:" ( "0" / "FFFF" ) ":" ip4addr
351      IP6ADDR = "(?:[#{HEXDIGIT}]+(?::[#{HEXDIGIT}]+){7}|0:0:0:0:0:(?:0|FFFF):#{IP4ADDR})"
352      # hostaddr   =  ip4addr / ip6addr
353      HOSTADDR = "(?:#{IP4ADDR}|#{IP6ADDR})"
354
355      # host       =  hostname / hostaddr
356      HOST = "(?:#{HOSTNAME}|#{HOSTADDR})"
357
358      # prefix     =  servername / ( nickname [ [ "!" user ] "@" host ] )
359      PREFIX = "(?:#{NICKNAME}(?:(?:!#{USER})?@#{HOST})?|#{SERVERNAME})"
360
361      # nospcrlfcl =  %x01-09 / %x0B-0C / %x0E-1F / %x21-39 / %x3B-FF
362      #                 ; any octet except NUL, CR, LF, " " and ":"
363      NOSPCRLFCL = '\x01-\x09\x0B-\x0C\x0E-\x1F\x21-\x39\x3B-\xFF'
364
365      # command    =  1*letter / 3digit
366      COMMAND = "(?:[#{LETTER}]+|[#{DIGIT}]{3})"
367
368      # SPACE      =  %x20        ; space character
369      # middle     =  nospcrlfcl *( ":" / nospcrlfcl )
370      # trailing   =  *( ":" / " " / nospcrlfcl )
371      # params     =  *14( SPACE middle ) [ SPACE ":" trailing ]
372      #            =/ 14( SPACE middle ) [ SPACE [ ":" ] trailing ]
373      MIDDLE = "[#{NOSPCRLFCL}][:#{NOSPCRLFCL}]*"
374      TRAILING = "[: #{NOSPCRLFCL}]*"
375      PARAMS = "(?:((?: #{MIDDLE}){0,14})(?: :(#{TRAILING}))?|((?: #{MIDDLE}){14})(?::?)?(#{TRAILING}))"
376
377      # crlf       =  %x0D %x0A   ; "carriage return" "linefeed"
378      # message    =  [ ":" prefix SPACE ] command [ params ] crlf
379      CRLF = '\x0D\x0A'
380      MESSAGE = "(?::(#{PREFIX}) )?(#{COMMAND})#{PARAMS}#{CRLF}"
381
382      CLIENT_PATTERN  = /\A#{NICKNAME}(?:(?:!#{USER})?@#{HOST})\z/on
383      MESSAGE_PATTERN = /\A#{MESSAGE}\z/on
384    end # PATTERN
385
386=begin
387
388--- RICE::Message::parse(str)
389
390=end
391
392    def self.parse(str)
393      unless PATTERN::MESSAGE_PATTERN =~ str
394        raise InvalidMessage, "Invalid message"
395
396      else
397        prefix  = $1
398        command = $2
399        if $3 && $3.size > 0
400          middle  = $3
401          trailer = $4
402        elsif $5 && $5.size > 0
403          middle  = $5
404          trailer = $6
405        elsif $4
406          params  = []
407          trailer = $4
408        elsif $6
409          params  = []
410          trailer = $6
411        else
412          params  = []
413        end
414      end
415      params ||= middle.split(/ /)[1..-1]
416      params << trailer if trailer
417
418      self.build(prefix, command, params)
419    end
420
421=begin
422
423--- RICE::Message::build(prefix, command, params)
424
425=end
426
427    def self.build(prefix, command, params)
428      if Command::Commands.include?(command)
429        Command::Commands[command].new(prefix, command, params)
430      elsif Reply::Replies.include?(command)
431        Reply::Replies[command].new(prefix, command, params)
432      else
433        raise UnknownCommand, "unknown command: #{command}"
434      end
435    end
436
437=begin
438
439--- RICE::Message#prefix
440
441--- RICE::Message#command
442
443--- RICE::Message#params
444
445=end
446
447    def initialize(prefix, command, params)
448      @prefix  = prefix
449      @command = command
450      @params  = params
451    end
452    attr_reader :prefix, :command, :params
453
454=begin
455
456--- RICE::Message::#to_s
457
458=end
459
460    def to_s
461      str = ''
462      if @prefix
463        str << ':'
464        str << @prefix
465        str << ' '
466      end
467
468      str << @command
469
470      if @params
471        f = false
472        @params.each do |param|
473          str << ' '
474          if !f && (param.size == 0 || / / =~ param || /^:/ =~ param)
475            str << ':'
476            str << param
477            f = true
478          else
479            str << param
480          end
481        end
482      end
483
484      str << "\x0D\x0A"
485
486      str
487    end
488
489=begin
490
491--- RICE::Message::#to_a
492
493=end
494
495    def to_a
496      [@prefix, @command, @params]
497    end
498
499    def inspect
500      sprintf('#<%s:0x%x prefix:%s command:%s params:%s>',
501              self.class, self.object_id, @prefix, @command, @params.inspect)
502    end
503
504  end # Message
505
506=begin
507
508== RICE::Command
509
510=end
511
512  module Command
513    class Command < Message
514    end # Command
515
516    Commands = {}
517    %w(PASS NICK USER OPER MODE SERVICE QUIT SQUIT
518       JOIN PART TOPIC NAMES LIST INVITE KICK
519       PRIVMSG NOTICE MOTD LUSERS VERSION STATS LINKS
520       TIME CONNECT TRACE ADMIN INFO SERVLIST SQUERY
521       WHO WHOIS WHOWAS KILL PING PONG ERROR
522       AWAY REHASH DIE RESTART SUMMON USERS WALLOPS USERHOST ISON
523    ).each do |cmd|
524      eval <<E
525      class #{cmd} < Command
526      end
527      Commands['#{cmd}'] = #{cmd}
528
529      def #{cmd.downcase}(*params)
530        #{cmd}.new(nil, '#{cmd}', params)
531      end
532      module_function :#{cmd.downcase}
533E
534    end
535
536    # XXX:
537    class PRIVMSG
538      def to_s
539        str = ''
540        if @prefix
541          str << ':'
542          str << @prefix
543          str << ' '
544        end
545
546        str << @command
547
548        str << ' '
549        str << @params[0]
550
551        str << ' :'
552        str << @params[1..-1].join(' ')
553
554        str << "\x0D\x0A"
555        str
556      end
557    end
558  end # Command
559
560=begin
561
562== RICE::Reply
563
564== RICE::CommandResponse
565
566== RICE::ErrorReply
567
568=end
569
570  module Reply
571    class Reply < Message
572    end
573
574    class CommandResponse < Reply
575    end
576
577    class ErrorReply < Reply
578    end
579
580    Replies = {}
581    %w(001,RPL_WELCOME 002,RPL_YOURHOST 003,RPL_CREATED
582       004,RPL_MYINFO 005,RPL_BOUNCE
583       302,RPL_USERHOST 303,RPL_ISON 301,RPL_AWAY
584       305,RPL_UNAWAY 306,RPL_NOWAWAY 311,RPL_WHOISUSER
585       312,RPL_WHOISSERVER 313,RPL_WHOISOPERATOR
586       317,RPL_WHOISIDLE 318,RPL_ENDOFWHOIS
587       319,RPL_WHOISCHANNELS 314,RPL_WHOWASUSER
588       369,RPL_ENDOFWHOWAS 321,RPL_LISTSTART
589       322,RPL_LIST 323,RPL_LISTEND 325,RPL_UNIQOPIS
590       324,RPL_CHANNELMODEIS 331,RPL_NOTOPIC
591       332,RPL_TOPIC 341,RPL_INVITING 342,RPL_SUMMONING
592       346,RPL_INVITELIST 347,RPL_ENDOFINVITELIST
593       348,RPL_EXCEPTLIST 349,RPL_ENDOFEXCEPTLIST
594       351,RPL_VERSION 352,RPL_WHOREPLY 315,RPL_ENDOFWHO
595       353,RPL_NAMREPLY 366,RPL_ENDOFNAMES 364,RPL_LINKS
596       365,RPL_ENDOFLINKS 367,RPL_BANLIST 368,RPL_ENDOFBANLIST
597       371,RPL_INFO 374,RPL_ENDOFINFO 375,RPL_MOTDSTART
598       372,RPL_MOTD 376,RPL_ENDOFMOTD 381,RPL_YOUREOPER
599       382,RPL_REHASHING 383,RPL_YOURESERVICE 391,RPL_TIM
600       392,RPL_ 393,RPL_USERS 394,RPL_ENDOFUSERS 395,RPL_NOUSERS
601       200,RPL_TRACELINK 201,RPL_TRACECONNECTING
602       202,RPL_TRACEHANDSHAKE 203,RPL_TRACEUNKNOWN
603       204,RPL_TRACEOPERATOR 205,RPL_TRACEUSER 206,RPL_TRACESERVER
604       207,RPL_TRACESERVICE 208,RPL_TRACENEWTYPE 209,RPL_TRACECLASS
605       210,RPL_TRACERECONNECT 261,RPL_TRACELOG 262,RPL_TRACEEND
606       211,RPL_STATSLINKINFO 212,RPL_STATSCOMMANDS 219,RPL_ENDOFSTATS
607       242,RPL_STATSUPTIME 243,RPL_STATSOLINE 221,RPL_UMODEIS
608       234,RPL_SERVLIST 235,RPL_SERVLISTEND 251,RPL_LUSERCLIENT
609       252,RPL_LUSEROP 253,RPL_LUSERUNKNOWN 254,RPL_LUSERCHANNELS
610       255,RPL_LUSERME 256,RPL_ADMINME 257,RPL_ADMINLOC1
611       258,RPL_ADMINLOC2 259,RPL_ADMINEMAIL 263,RPL_TRYAGAIN
612       401,ERR_NOSUCHNICK 402,ERR_NOSUCHSERVER 403,ERR_NOSUCHCHANNEL
613       404,ERR_CANNOTSENDTOCHAN 405,ERR_TOOMANYCHANNELS
614       406,ERR_WASNOSUCHNICK 407,ERR_TOOMANYTARGETS
615       408,ERR_NOSUCHSERVICE 409,ERR_NOORIGIN 411,ERR_NORECIPIENT
616       412,ERR_NOTEXTTOSEND 413,ERR_NOTOPLEVEL 414,ERR_WILDTOPLEVEL
617       415,ERR_BADMASK 421,ERR_UNKNOWNCOMMAND 422,ERR_NOMOTD
618       423,ERR_NOADMININFO 424,ERR_FILEERROR 431,ERR_NONICKNAMEGIVEN
619       432,ERR_ERRONEUSNICKNAME 433,ERR_NICKNAMEINUSE
620       436,ERR_NICKCOLLISION 437,ERR_UNAVAILRESOURCE
621       441,ERR_USERNOTINCHANNEL 442,ERR_NOTONCHANNEL
622       443,ERR_USERONCHANNEL 444,ERR_NOLOGIN 445,ERR_SUMMONDISABLED
623       446,ERR_USERSDISABLED 451,ERR_NOTREGISTERED
624       461,ERR_NEEDMOREPARAMS 462,ERR_ALREADYREGISTRED
625       463,ERR_NOPERMFORHOST 464,ERR_PASSWDMISMATCH
626       465,ERR_YOUREBANNEDCREEP 466,ERR_YOUWILLBEBANNED
627       467,ERR_KEYSE 471,ERR_CHANNELISFULL 472,ERR_UNKNOWNMODE
628       473,ERR_INVITEONLYCHAN 474,ERR_BANNEDFROMCHAN
629       475,ERR_BADCHANNELKEY 476,ERR_BADCHANMASK 477,ERR_NOCHANMODES
630       478,ERR_BANLISTFULL 481,ERR_NOPRIVILEGES 482,ERR_CHANOPRIVSNEEDED
631       483,ERR_CANTKILLSERVER 484,ERR_RESTRICTED
632       485,ERR_UNIQOPPRIVSNEEDED 491,ERR_NOOPERHOST
633       501,ERR_UMODEUNKNOWNFLAG 502,ERR_USERSDONTMATCH
634       231,RPL_SERVICEINFO 232,RPL_ENDOFSERVICES
635       233,RPL_SERVICE 300,RPL_NONE 316,RPL_WHOISCHANOP
636       361,RPL_KILLDONE 362,RPL_CLOSING 363,RPL_CLOSEEND
637       373,RPL_INFOSTART 384,RPL_MYPORTIS 213,RPL_STATSCLINE
638       214,RPL_STATSNLINE 215,RPL_STATSILINE 216,RPL_STATSKLINE
639       217,RPL_STATSQLINE 218,RPL_STATSYLINE 240,RPL_STATSVLINE
640       241,RPL_STATSLLINE 244,RPL_STATSHLINE 244,RPL_STATSSLINE
641       246,RPL_STATSPING 247,RPL_STATSBLINE 250,RPL_STATSDLINE
642       492,ERR_NOSERVICEHOST
643    ).each do |num_cmd|
644      num, cmd = num_cmd.split(',', 2)
645      eval <<E
646      class #{cmd} < #{if num[0] == ?0 || num[0] == ?2 || num[0] == ?3
647                        'CommandResponse'
648                       elsif num[0] == ?4 || num[0] == ?5
649                        'ErrorReply'
650                       end}
651      end
652      Replies['#{num}'] = #{cmd}
653
654      def #{cmd.downcase}(prefix, *params)
655        #{cmd}.new(prefix, '#{num}', params)
656      end
657      module_function :#{cmd.downcase}
658E
659    end
660  end # Reply
661end # RICE
Note: See TracBrowser for help on using the browser.