| 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 | |
|---|
| 12 | require 'socket' |
|---|
| 13 | require 'thread' |
|---|
| 14 | require 'monitor' |
|---|
| 15 | |
|---|
| 16 | module 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} |
|---|
| 533 | E |
|---|
| 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} |
|---|
| 658 | E |
|---|
| 659 | end |
|---|
| 660 | end # Reply |
|---|
| 661 | end # RICE |
|---|