net-irc2 0.0.10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/AUTHORS.txt +33 -0
- data/ChangeLog +95 -0
- data/README +91 -0
- data/Rakefile +69 -0
- data/examples/2ch.rb +225 -0
- data/examples/2ig.rb +267 -0
- data/examples/client.rb +23 -0
- data/examples/echo_bot.rb +31 -0
- data/examples/echo_bot_celluloid.rb +33 -0
- data/examples/gig.rb +192 -0
- data/examples/gmail.rb +202 -0
- data/examples/gtig.rb +420 -0
- data/examples/hatena-star-stream.rb +270 -0
- data/examples/hcig.rb +285 -0
- data/examples/hig.rb +771 -0
- data/examples/iig.rb +819 -0
- data/examples/ircd.rb +358 -0
- data/examples/lig.rb +551 -0
- data/examples/lingr.rb +327 -0
- data/examples/mixi.rb +252 -0
- data/examples/sig.rb +188 -0
- data/examples/tig.rb +2712 -0
- data/lib/net/irc/client/channel_manager.rb +144 -0
- data/lib/net/irc/client.rb +117 -0
- data/lib/net/irc/constants.rb +214 -0
- data/lib/net/irc/message/modeparser.rb +85 -0
- data/lib/net/irc/message/serverconfig.rb +30 -0
- data/lib/net/irc/message.rb +109 -0
- data/lib/net/irc/pattern.rb +68 -0
- data/lib/net/irc/server.rb +186 -0
- data/lib/net/irc.rb +77 -0
- data/spec/channel_manager_spec.rb +184 -0
- data/spec/modeparser_spec.rb +165 -0
- data/spec/net-irc_spec.rb +337 -0
- data/spec/spec.opts +1 -0
- metadata +91 -0
@@ -0,0 +1,68 @@
|
|
1
|
+
# coding: ASCII-8BIT
|
2
|
+
module Net::IRC::PATTERN # :nodoc:
|
3
|
+
# letter = %x41-5A / %x61-7A ; A-Z / a-z
|
4
|
+
# digit = %x30-39 ; 0-9
|
5
|
+
# hexdigit = digit / "A" / "B" / "C" / "D" / "E" / "F"
|
6
|
+
# special = %x5B-60 / %x7B-7D
|
7
|
+
# ; "[", "]", "\", "`", "_", "^", "{", "|", "}"
|
8
|
+
LETTER = 'A-Za-z'
|
9
|
+
DIGIT = '0-9'
|
10
|
+
HEXDIGIT = "#{DIGIT}A-Fa-f"
|
11
|
+
SPECIAL = '\x5B-\x60\x7B-\x7D'
|
12
|
+
|
13
|
+
# shortname = ( letter / digit ) *( letter / digit / "-" )
|
14
|
+
# *( letter / digit )
|
15
|
+
# ; as specified in RFC 1123 [HNAME]
|
16
|
+
# hostname = shortname *( "." shortname )
|
17
|
+
SHORTNAME = "[#{LETTER}#{DIGIT}](?:[-#{LETTER}#{DIGIT}]*[#{LETTER}#{DIGIT}])?"
|
18
|
+
HOSTNAME = "#{SHORTNAME}(?:\\.#{SHORTNAME})*"
|
19
|
+
|
20
|
+
# servername = hostname
|
21
|
+
SERVERNAME = HOSTNAME
|
22
|
+
|
23
|
+
# nickname = ( letter / special ) *8( letter / digit / special / "-" )
|
24
|
+
#NICKNAME = "[#{LETTER}#{SPECIAL}#{DIGIT}_][-#{LETTER}#{DIGIT}#{SPECIAL}]*"
|
25
|
+
NICKNAME = "\\S+" # for multibytes
|
26
|
+
|
27
|
+
# user = 1*( %x01-09 / %x0B-0C / %x0E-1F / %x21-3F / %x41-FF )
|
28
|
+
# ; any octet except NUL, CR, LF, " " and "@"
|
29
|
+
USER = '[\x01-\x09\x0B-\x0C\x0E-\x1F\x21-\x3F\x41-\xFF]+'
|
30
|
+
|
31
|
+
# ip4addr = 1*3digit "." 1*3digit "." 1*3digit "." 1*3digit
|
32
|
+
IP4ADDR = "[#{DIGIT}]{1,3}(?:\\.[#{DIGIT}]{1,3}){3}"
|
33
|
+
# ip6addr = 1*hexdigit 7( ":" 1*hexdigit )
|
34
|
+
# ip6addr =/ "0:0:0:0:0:" ( "0" / "FFFF" ) ":" ip4addr
|
35
|
+
IP6ADDR = "(?:[#{HEXDIGIT}]+(?::[#{HEXDIGIT}]+){7}|0:0:0:0:0:(?:0|FFFF):#{IP4ADDR})"
|
36
|
+
# hostaddr = ip4addr / ip6addr
|
37
|
+
HOSTADDR = "(?:#{IP4ADDR}|#{IP6ADDR})"
|
38
|
+
|
39
|
+
# host = hostname / hostaddr
|
40
|
+
HOST = "(?:#{HOSTNAME}|#{HOSTADDR})"
|
41
|
+
|
42
|
+
# prefix = servername / ( nickname [ [ "!" user ] "@" host ] )
|
43
|
+
PREFIX = "(?:#{NICKNAME}(?:(?:!#{USER})?@#{HOST})?|#{SERVERNAME})"
|
44
|
+
|
45
|
+
# nospcrlfcl = %x01-09 / %x0B-0C / %x0E-1F / %x21-39 / %x3B-FF
|
46
|
+
# ; any octet except NUL, CR, LF, " " and ":"
|
47
|
+
NOSPCRLFCL = '\x01-\x09\x0B-\x0C\x0E-\x1F\x21-\x39\x3B-\xFF'
|
48
|
+
|
49
|
+
# command = 1*letter / 3digit
|
50
|
+
COMMAND = "(?:[#{LETTER}]+|[#{DIGIT}]{3})"
|
51
|
+
|
52
|
+
# SPACE = %x20 ; space character
|
53
|
+
# middle = nospcrlfcl *( ":" / nospcrlfcl )
|
54
|
+
# trailing = *( ":" / " " / nospcrlfcl )
|
55
|
+
# params = *14( SPACE middle ) [ SPACE ":" trailing ]
|
56
|
+
# =/ 14( SPACE middle ) [ SPACE [ ":" ] trailing ]
|
57
|
+
MIDDLE = "[#{NOSPCRLFCL}][:#{NOSPCRLFCL}]*"
|
58
|
+
TRAILING = "[: #{NOSPCRLFCL}]*"
|
59
|
+
PARAMS = "(?:((?: #{MIDDLE}){0,14})(?: :(#{TRAILING}))?|((?: #{MIDDLE}){14}):?(#{TRAILING}))"
|
60
|
+
|
61
|
+
# crlf = %x0D %x0A ; "carriage return" "linefeed"
|
62
|
+
# message = [ ":" prefix SPACE ] command [ params ] crlf
|
63
|
+
CRLF = '\x0D\x0A'
|
64
|
+
MESSAGE = "(?::(#{PREFIX}) )?(#{COMMAND})#{PARAMS}\s*#{CRLF}"
|
65
|
+
|
66
|
+
CLIENT_PATTERN = /\A#{NICKNAME}(?:(?:!#{USER})?@#{HOST})\z/on
|
67
|
+
MESSAGE_PATTERN = /\A#{MESSAGE}\z/on
|
68
|
+
end # PATTERN
|
@@ -0,0 +1,186 @@
|
|
1
|
+
class Net::IRC::Server
|
2
|
+
# Server global state for accessing Server::Session
|
3
|
+
attr_accessor :state
|
4
|
+
attr_accessor :sessions
|
5
|
+
|
6
|
+
def initialize(host, port, session_class, opts={})
|
7
|
+
@host = host
|
8
|
+
@port = port
|
9
|
+
@session_class = session_class
|
10
|
+
@opts = OpenStruct.new(opts)
|
11
|
+
@sessions = []
|
12
|
+
@state = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
# Start server loop.
|
16
|
+
def start
|
17
|
+
@serv = TCPServer.new(@host, @port)
|
18
|
+
@log = @opts.logger || Logger.new($stdout)
|
19
|
+
@log.info "Host: #{@host} Port:#{@port}"
|
20
|
+
@accept = Thread.start do
|
21
|
+
loop do
|
22
|
+
Thread.start(@serv.accept) do |s|
|
23
|
+
begin
|
24
|
+
@log.info "Client connected, new session starting..."
|
25
|
+
s = @session_class.new(self, s, @log, @opts)
|
26
|
+
@sessions << s
|
27
|
+
s.start
|
28
|
+
rescue Exception => e
|
29
|
+
puts e
|
30
|
+
puts e.backtrace
|
31
|
+
ensure
|
32
|
+
@sessions.delete(s)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
@accept.join
|
38
|
+
end
|
39
|
+
|
40
|
+
# Close all sessions.
|
41
|
+
def finish
|
42
|
+
Thread.exclusive do
|
43
|
+
@accept.kill
|
44
|
+
begin
|
45
|
+
@serv.close
|
46
|
+
rescue
|
47
|
+
end
|
48
|
+
@sessions.each do |s|
|
49
|
+
s.finish
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
class Session
|
56
|
+
include Net::IRC
|
57
|
+
include Constants
|
58
|
+
|
59
|
+
attr_reader :prefix, :nick, :real, :host
|
60
|
+
|
61
|
+
# Override subclass.
|
62
|
+
def server_name
|
63
|
+
"net-irc"
|
64
|
+
end
|
65
|
+
|
66
|
+
# Override subclass.
|
67
|
+
def server_version
|
68
|
+
"0.0.0"
|
69
|
+
end
|
70
|
+
|
71
|
+
# Override subclass.
|
72
|
+
def available_user_modes
|
73
|
+
"eixwy"
|
74
|
+
end
|
75
|
+
|
76
|
+
# Override subclass.
|
77
|
+
def available_channel_modes
|
78
|
+
"spknm"
|
79
|
+
end
|
80
|
+
|
81
|
+
def initialize(server, socket, logger, opts={})
|
82
|
+
@server, @socket, @log, @opts = server, socket, logger, opts
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.start(*args)
|
86
|
+
new(*args).start
|
87
|
+
end
|
88
|
+
|
89
|
+
# Start session loop.
|
90
|
+
def start
|
91
|
+
on_connected
|
92
|
+
while l = @socket.gets
|
93
|
+
begin
|
94
|
+
@log.debug "RECEIVE: #{l.chomp}"
|
95
|
+
m = Message.parse(l)
|
96
|
+
next if on_message(m) === true
|
97
|
+
|
98
|
+
name = "on_#{(COMMANDS[m.command.upcase] || m.command).downcase}"
|
99
|
+
send(name, m) if respond_to?(name)
|
100
|
+
|
101
|
+
break if m.command == QUIT
|
102
|
+
rescue Message::InvalidMessage
|
103
|
+
@log.error "MessageParse: " + l.inspect
|
104
|
+
end
|
105
|
+
end
|
106
|
+
rescue IOError
|
107
|
+
ensure
|
108
|
+
finish
|
109
|
+
end
|
110
|
+
|
111
|
+
# Close this session.
|
112
|
+
def finish
|
113
|
+
begin
|
114
|
+
@socket.close
|
115
|
+
rescue
|
116
|
+
end
|
117
|
+
on_disconnected
|
118
|
+
end
|
119
|
+
|
120
|
+
# Default PASS callback.
|
121
|
+
# Set @pass.
|
122
|
+
def on_pass(m)
|
123
|
+
@pass = m.params[0]
|
124
|
+
end
|
125
|
+
|
126
|
+
# Default NICK callback.
|
127
|
+
# Set @nick.
|
128
|
+
def on_nick(m)
|
129
|
+
@nick = m.params[0]
|
130
|
+
@prefix = Prefix.new("#{@nick}!#{@user}@#{@host}") if defined? @prefix
|
131
|
+
end
|
132
|
+
|
133
|
+
# Default USER callback.
|
134
|
+
# Set @user, @real, @host and call initial_message.
|
135
|
+
def on_user(m)
|
136
|
+
@user, @real = m.params[0], m.params[3]
|
137
|
+
@nick ||= @user
|
138
|
+
@host = @socket.peeraddr[2]
|
139
|
+
@prefix = Prefix.new("#{@nick}!#{@user}@#{@host}")
|
140
|
+
initial_message
|
141
|
+
end
|
142
|
+
|
143
|
+
# Call when socket connected.
|
144
|
+
def on_connected
|
145
|
+
end
|
146
|
+
|
147
|
+
# Call when socket closed.
|
148
|
+
def on_disconnected
|
149
|
+
end
|
150
|
+
|
151
|
+
# Catch all messages.
|
152
|
+
# If this method return true, aother callback will not be called.
|
153
|
+
def on_message(m)
|
154
|
+
end
|
155
|
+
|
156
|
+
# Default PING callback. Response PONG.
|
157
|
+
def on_ping(m)
|
158
|
+
post server_name, PONG, m.params[0]
|
159
|
+
end
|
160
|
+
|
161
|
+
private
|
162
|
+
# Post message to server.
|
163
|
+
#
|
164
|
+
# include Net::IRC::Constants
|
165
|
+
# post prefix, PRIVMSG, "#channel", "foobar"
|
166
|
+
def post(prefix, command, *params)
|
167
|
+
m = Message.new(prefix, command, params.map {|s|
|
168
|
+
#s.gsub(/\r\n|[\r\n]/, " ")
|
169
|
+
s.tr("\r\n", " ")
|
170
|
+
})
|
171
|
+
@log.debug "SEND: #{m.to_s.chomp}"
|
172
|
+
@socket << m
|
173
|
+
rescue IOError
|
174
|
+
finish
|
175
|
+
end
|
176
|
+
|
177
|
+
# Call when client connected.
|
178
|
+
# Send RPL_WELCOME sequence. If you want to customize, override this method at subclass.
|
179
|
+
def initial_message
|
180
|
+
post server_name, RPL_WELCOME, @nick, "Welcome to the Internet Relay Network #{@prefix}"
|
181
|
+
post server_name, RPL_YOURHOST, @nick, "Your host is #{server_name}, running version #{server_version}"
|
182
|
+
post server_name, RPL_CREATED, @nick, "This server was created #{Time.now}"
|
183
|
+
post server_name, RPL_MYINFO, @nick, "#{server_name} #{server_version} #{available_user_modes} #{available_channel_modes}"
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end # Server
|
data/lib/net/irc.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
#!ruby
|
2
|
+
|
3
|
+
require "bundler"
|
4
|
+
Bundler.setup
|
5
|
+
|
6
|
+
require "ostruct"
|
7
|
+
require "socket"
|
8
|
+
require "logger"
|
9
|
+
require 'buftok'
|
10
|
+
|
11
|
+
module Net; end
|
12
|
+
|
13
|
+
module Net::IRC
|
14
|
+
VERSION = "0.0.10".freeze
|
15
|
+
class IRCException < StandardError; end
|
16
|
+
|
17
|
+
require "net/irc/constants"
|
18
|
+
require "net/irc/pattern"
|
19
|
+
|
20
|
+
autoload :Message, "net/irc/message"
|
21
|
+
autoload :Client, "net/irc/client"
|
22
|
+
autoload :Server, "net/irc/server"
|
23
|
+
|
24
|
+
class Prefix < String
|
25
|
+
def nick
|
26
|
+
extract[0]
|
27
|
+
end
|
28
|
+
|
29
|
+
def user
|
30
|
+
extract[1]
|
31
|
+
end
|
32
|
+
|
33
|
+
def host
|
34
|
+
extract[2]
|
35
|
+
end
|
36
|
+
|
37
|
+
# Extract Prefix String to [nick, user, host] Array.
|
38
|
+
def extract
|
39
|
+
_, *ret = *self.match(/\A([^\s!]+)(?:!([^\s@]+)@(\S+))?\z/)
|
40
|
+
ret
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Encode to CTCP message. Prefix and postfix \x01.
|
45
|
+
def ctcp_encode(str)
|
46
|
+
"\x01#{ctcp_quote(str)}\x01"
|
47
|
+
end
|
48
|
+
#alias :ctcp_encoding :ctcp_encode
|
49
|
+
module_function :ctcp_encode #, :ctcp_encoding
|
50
|
+
|
51
|
+
# Decode from CTCP message delimited with \x01.
|
52
|
+
def ctcp_decode(str)
|
53
|
+
ctcp_dequote(str.delete("\x01"))
|
54
|
+
end
|
55
|
+
#alias :ctcp_decoding :ctcp_decode
|
56
|
+
module_function :ctcp_decode #, :ctcp_decoding
|
57
|
+
|
58
|
+
def ctcp_quote(str)
|
59
|
+
low_quote(str.gsub("\\", "\\\\\\\\").gsub("\x01", "\\a"))
|
60
|
+
end
|
61
|
+
module_function :ctcp_quote
|
62
|
+
|
63
|
+
def ctcp_dequote(str)
|
64
|
+
low_dequote(str).gsub("\\a", "\x01").gsub(/\\(.|\z)/m, "\\1")
|
65
|
+
end
|
66
|
+
module_function :ctcp_dequote
|
67
|
+
|
68
|
+
private
|
69
|
+
def low_quote(str)
|
70
|
+
str.gsub("\x10", "\x10\x10").gsub("\x00", "\x10\x30").gsub("\r", "\x10r").gsub("\n", "\x10n")
|
71
|
+
end
|
72
|
+
|
73
|
+
def low_dequote(str)
|
74
|
+
str.gsub("\x10n", "\n").gsub("\x10r", "\r").gsub("\x10\x30", "\x00").gsub("\x10\x10", "\x10")
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
@@ -0,0 +1,184 @@
|
|
1
|
+
#!spec
|
2
|
+
|
3
|
+
$LOAD_PATH << "lib"
|
4
|
+
$LOAD_PATH << "../lib"
|
5
|
+
|
6
|
+
require "rubygems"
|
7
|
+
require "rspec"
|
8
|
+
require "thread"
|
9
|
+
require "net/irc"
|
10
|
+
require "net/irc/client/channel_manager"
|
11
|
+
include Net::IRC
|
12
|
+
include Constants
|
13
|
+
|
14
|
+
class ChannelManagerTestServerSession < Net::IRC::Server::Session
|
15
|
+
@@testq = SizedQueue.new(1)
|
16
|
+
@@instance = nil
|
17
|
+
|
18
|
+
def self.testq
|
19
|
+
@@testq
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.instance
|
23
|
+
@@instance
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(*args)
|
27
|
+
super
|
28
|
+
@@instance = self
|
29
|
+
end
|
30
|
+
|
31
|
+
def on_message(m)
|
32
|
+
@@testq << m
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class ChannelManagerTestClient < Net::IRC::Client
|
37
|
+
include Net::IRC::Client::ChannelManager
|
38
|
+
@@testq = SizedQueue.new(1)
|
39
|
+
|
40
|
+
def self.testq
|
41
|
+
@@testq
|
42
|
+
end
|
43
|
+
|
44
|
+
def on_message(m)
|
45
|
+
@@testq << m
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe Net::IRC, "server and client" do
|
50
|
+
before :all do
|
51
|
+
@port = nil
|
52
|
+
@server, @client = nil, nil
|
53
|
+
|
54
|
+
Thread.abort_on_exception = true
|
55
|
+
Thread.start do
|
56
|
+
@server = Net::IRC::Server.new("localhost", @port, ChannelManagerTestServerSession, {
|
57
|
+
:logger => Logger.new(nil),
|
58
|
+
})
|
59
|
+
@server.start
|
60
|
+
end
|
61
|
+
|
62
|
+
Thread.pass
|
63
|
+
true until @server.instance_variable_get(:@serv)
|
64
|
+
|
65
|
+
@port = @server.instance_variable_get(:@serv).addr[1]
|
66
|
+
|
67
|
+
Thread.start do
|
68
|
+
@client = ChannelManagerTestClient.new("localhost", @port, {
|
69
|
+
:nick => "foonick",
|
70
|
+
:user => "foouser",
|
71
|
+
:real => "foo real name",
|
72
|
+
:pass => "foopass",
|
73
|
+
:logger => Logger.new(nil),
|
74
|
+
})
|
75
|
+
@client.start
|
76
|
+
end
|
77
|
+
|
78
|
+
Thread.pass
|
79
|
+
true until @client
|
80
|
+
end
|
81
|
+
|
82
|
+
server_q = ChannelManagerTestServerSession.testq
|
83
|
+
client_q = ChannelManagerTestClient.testq
|
84
|
+
|
85
|
+
it "client should manage channel mode/users correctly" do
|
86
|
+
client = @client
|
87
|
+
client.instance_variable_set(:@prefix, Prefix.new("foonick!foouser@localhost"))
|
88
|
+
|
89
|
+
true until ChannelManagerTestServerSession.instance
|
90
|
+
ChannelManagerTestServerSession.instance.instance_eval do
|
91
|
+
Thread.exclusive do
|
92
|
+
post client.prefix, JOIN, "#test"
|
93
|
+
post nil, NOTICE, "#test", "sep1"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
true until client_q.pop.to_s == "NOTICE #test sep1\r\n"
|
98
|
+
|
99
|
+
c = @client.instance_variable_get(:@channels)
|
100
|
+
c.synchronize do
|
101
|
+
c.should be_a_kind_of(Hash)
|
102
|
+
c["#test"].should be_a_kind_of(Hash)
|
103
|
+
c["#test"][:modes].should be_a_kind_of(Array)
|
104
|
+
c["#test"][:users].should be_a_kind_of(Array)
|
105
|
+
c["#test"][:users].should == ["foonick"]
|
106
|
+
end
|
107
|
+
|
108
|
+
ChannelManagerTestServerSession.instance.instance_eval do
|
109
|
+
Thread.exclusive do
|
110
|
+
post "test1!test@localhost", JOIN, "#test"
|
111
|
+
post "test2!test@localhost", JOIN, "#test"
|
112
|
+
post nil, NOTICE, "#test", "sep2"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
true until client_q.pop.to_s == "NOTICE #test sep2\r\n"
|
117
|
+
c.synchronize do
|
118
|
+
c["#test"][:users].should == ["foonick", "test1", "test2"]
|
119
|
+
end
|
120
|
+
|
121
|
+
ChannelManagerTestServerSession.instance.instance_eval do
|
122
|
+
Thread.exclusive do
|
123
|
+
post nil, RPL_NAMREPLY, client.prefix.nick, "@", "#test", "foo1 foo2 foo3 @foo4 +foo5"
|
124
|
+
post nil, NOTICE, "#test", "sep3"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
true until client_q.pop.to_s == "NOTICE #test sep3\r\n"
|
129
|
+
c.synchronize do
|
130
|
+
c["#test"][:users].should == ["foonick", "test1", "test2", "foo1", "foo2", "foo3", "foo4", "foo5"]
|
131
|
+
c["#test"][:modes].should include([:s, nil])
|
132
|
+
c["#test"][:modes].should include([:o, "foo4"])
|
133
|
+
c["#test"][:modes].should include([:v, "foo5"])
|
134
|
+
end
|
135
|
+
|
136
|
+
ChannelManagerTestServerSession.instance.instance_eval do
|
137
|
+
Thread.exclusive do
|
138
|
+
post nil, RPL_NAMREPLY, client.prefix.nick, "@", "#test1", "foo1 foo2 foo3 @foo4 +foo5"
|
139
|
+
post "foo4!foo@localhost", QUIT, "message"
|
140
|
+
post "foo5!foo@localhost", PART, "#test1", "message"
|
141
|
+
post client.prefix, KICK, "#test", "foo1", "message"
|
142
|
+
post client.prefix, MODE, "#test", "+o", "foo2"
|
143
|
+
post nil, NOTICE, "#test", "sep4"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
true until client_q.pop.to_s == "NOTICE #test sep4\r\n"
|
148
|
+
c.synchronize do
|
149
|
+
c["#test"][:users].should == ["foonick", "test1", "test2", "foo2", "foo3", "foo5"]
|
150
|
+
c["#test1"][:users].should == ["foo1", "foo2", "foo3"]
|
151
|
+
c["#test"][:modes].should_not include([:o, "foo4"])
|
152
|
+
c["#test"][:modes].should include([:v, "foo5"])
|
153
|
+
c["#test1"][:modes].should_not include([:v, "foo5"])
|
154
|
+
c["#test"][:modes].should include([:o, "foo2"])
|
155
|
+
end
|
156
|
+
|
157
|
+
ChannelManagerTestServerSession.instance.instance_eval do
|
158
|
+
Thread.exclusive do
|
159
|
+
post "foonick!test@localhost", NICK, "foonick2"
|
160
|
+
post "foonick2!test@localhost", NICK, "foonick"
|
161
|
+
post "foo2!test@localhost", NICK, "bar2"
|
162
|
+
post "foo3!test@localhost", NICK, "bar3"
|
163
|
+
post nil, NOTICE, "#test", "sep5"
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
true until client_q.pop.to_s == "NOTICE #test sep5\r\n"
|
168
|
+
c.synchronize do
|
169
|
+
c["#test"][:users].should == ["foonick", "test1", "test2", "bar2", "bar3", "foo5"]
|
170
|
+
c["#test1"][:users].should == ["foo1", "bar2", "bar3"]
|
171
|
+
c["#test"][:modes].should_not include([:o, "foo4"])
|
172
|
+
c["#test"][:modes].should include([:v, "foo5"])
|
173
|
+
c["#test1"][:modes].should_not include([:v, "foo5"])
|
174
|
+
c["#test"][:modes].should_not include([:o, "foo2"])
|
175
|
+
c["#test"][:modes].should include([:o, "bar2"])
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
after :all do
|
180
|
+
@server.finish
|
181
|
+
@client.finish
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
@@ -0,0 +1,165 @@
|
|
1
|
+
|
2
|
+
$LOAD_PATH << "lib"
|
3
|
+
$LOAD_PATH << "../lib"
|
4
|
+
|
5
|
+
require "net/irc"
|
6
|
+
include Net::IRC
|
7
|
+
include Constants
|
8
|
+
|
9
|
+
describe Message::ModeParser do
|
10
|
+
it "should parse RFC2812+ correctly" do
|
11
|
+
parser = Message::ModeParser.new
|
12
|
+
|
13
|
+
parser.parse("#Finish +im")[:positive].should == [[:i, nil], [:m, nil]]
|
14
|
+
parser.parse("#Finish +o Kilroy")[:positive].should == [[:o, "Kilroy"]]
|
15
|
+
parser.parse("#Finish +v Kilroy")[:positive].should == [[:v, "Kilroy"]]
|
16
|
+
parser.parse("#Fins -s")[:negative].should == [[:s, nil]]
|
17
|
+
parser.parse("#42 +k oulu")[:positive].should == [[:k, "oulu"]]
|
18
|
+
parser.parse("#eu-opers +l 10")[:positive].should == [[:l, "10"]]
|
19
|
+
parser.parse("&oulu +b")[:positive].should == [[:b, nil]]
|
20
|
+
parser.parse("&oulu +b *!*@*")[:positive].should == [[:b, "*!*@*"]]
|
21
|
+
|
22
|
+
parser.parse("&oulu +b *!*@*.edu")[:positive].should == [[:b, "*!*@*.edu"]]
|
23
|
+
parser.parse("#oulu +e")[:positive].should == [[:e, nil]]
|
24
|
+
parser.parse("#oulu +e *!*@*.edu")[:positive].should == [[:e, "*!*@*.edu"]]
|
25
|
+
parser.parse("#oulu +I")[:positive].should == [[:I, nil]]
|
26
|
+
parser.parse("#oulu +I *!*@*.edu")[:positive].should == [[:I, "*!*@*.edu"]]
|
27
|
+
parser.parse("#oulu +R")[:positive].should == [[:R, nil]]
|
28
|
+
parser.parse("#oulu +R *!*@*.edu")[:positive].should == [[:R, "*!*@*.edu"]]
|
29
|
+
|
30
|
+
parser.parse("#foo +ooo foo bar baz").should == {
|
31
|
+
:positive => [[:o, "foo"], [:o, "bar"], [:o, "baz"]],
|
32
|
+
:negative => [],
|
33
|
+
}
|
34
|
+
parser.parse("#foo +oo-o foo bar baz").should == {
|
35
|
+
:positive => [[:o, "foo"], [:o, "bar"]],
|
36
|
+
:negative => [[:o, "baz"]],
|
37
|
+
}
|
38
|
+
parser.parse("#foo -oo+o foo bar baz").should == {
|
39
|
+
:positive => [[:o, "baz"]],
|
40
|
+
:negative => [[:o, "foo"], [:o, "bar"]],
|
41
|
+
}
|
42
|
+
parser.parse("#foo +imv foo").should == {
|
43
|
+
:positive => [[:i, nil], [:m, nil], [:v, "foo"]],
|
44
|
+
:negative => [],
|
45
|
+
}
|
46
|
+
|
47
|
+
parser.parse("#foo +lk 10 foo").should == {
|
48
|
+
:positive => [[:l, "10"], [:k, "foo"]],
|
49
|
+
:negative => [],
|
50
|
+
}
|
51
|
+
parser.parse("#foo -l+k foo").should == {
|
52
|
+
:positive => [[:k, "foo"]],
|
53
|
+
:negative => [[:l, nil]],
|
54
|
+
}
|
55
|
+
parser.parse("#foo +ao foo").should == {
|
56
|
+
:positive => [[:a, nil], [:o, "foo"]],
|
57
|
+
:negative => [],
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should parse modes of Hyperion ircd correctly" do
|
62
|
+
parser = Message::ModeParser.new
|
63
|
+
parser.set(:CHANMODES, 'bdeIq,k,lfJD,cgijLmnPQrRstz')
|
64
|
+
|
65
|
+
parser.parse("#Finish +im")[:positive].should == [[:i, nil], [:m, nil]]
|
66
|
+
parser.parse("#Finish +o Kilroy")[:positive].should == [[:o, "Kilroy"]]
|
67
|
+
parser.parse("#Finish +v Kilroy")[:positive].should == [[:v, "Kilroy"]]
|
68
|
+
parser.parse("#Fins -s")[:negative].should == [[:s, nil]]
|
69
|
+
parser.parse("#42 +k oulu")[:positive].should == [[:k, "oulu"]]
|
70
|
+
parser.parse("#eu-opers +l 10")[:positive].should == [[:l, "10"]]
|
71
|
+
parser.parse("&oulu +b")[:positive].should == [[:b, nil]]
|
72
|
+
parser.parse("&oulu +b *!*@*")[:positive].should == [[:b, "*!*@*"]]
|
73
|
+
parser.parse("&oulu +b *!*@*.edu")[:positive].should == [[:b, "*!*@*.edu"]]
|
74
|
+
|
75
|
+
parser.parse("#oulu +e")[:positive].should == [[:e, nil]]
|
76
|
+
parser.parse("#oulu +e *!*@*.edu")[:positive].should == [[:e, "*!*@*.edu"]]
|
77
|
+
parser.parse("#oulu +I")[:positive].should == [[:I, nil]]
|
78
|
+
parser.parse("#oulu +I *!*@*.edu")[:positive].should == [[:I, "*!*@*.edu"]]
|
79
|
+
|
80
|
+
parser.parse("#foo +ooo foo bar baz").should == {
|
81
|
+
:positive => [[:o, "foo"], [:o, "bar"], [:o, "baz"]],
|
82
|
+
:negative => [],
|
83
|
+
}
|
84
|
+
parser.parse("#foo +oo-o foo bar baz").should == {
|
85
|
+
:positive => [[:o, "foo"], [:o, "bar"]],
|
86
|
+
:negative => [[:o, "baz"]],
|
87
|
+
}
|
88
|
+
parser.parse("#foo -oo+o foo bar baz").should == {
|
89
|
+
:positive => [[:o, "baz"]],
|
90
|
+
:negative => [[:o, "foo"], [:o, "bar"]],
|
91
|
+
}
|
92
|
+
parser.parse("#foo +imv foo").should == {
|
93
|
+
:positive => [[:i, nil], [:m, nil], [:v, "foo"]],
|
94
|
+
:negative => [],
|
95
|
+
}
|
96
|
+
|
97
|
+
parser.parse("#foo +lk 10 foo").should == {
|
98
|
+
:positive => [[:l, "10"], [:k, "foo"]],
|
99
|
+
:negative => [],
|
100
|
+
}
|
101
|
+
parser.parse("#foo -l+k foo").should == {
|
102
|
+
:positive => [[:k, "foo"]],
|
103
|
+
:negative => [[:l, nil]],
|
104
|
+
}
|
105
|
+
parser.parse("#foo +cv foo").should == {
|
106
|
+
:positive => [[:c, nil], [:v, "foo"]],
|
107
|
+
:negative => [],
|
108
|
+
}
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should parse modes of Unreal ircd correctly" do
|
112
|
+
parser = Message::ModeParser.new
|
113
|
+
parser.set(:PREFIX, '(qaohv)~&@%+')
|
114
|
+
parser.set(:CHANMODES, 'beI,kfL,lj,psmntirRcOAQKVCuzNSMTG')
|
115
|
+
|
116
|
+
parser.parse("#Finish +im")[:positive].should == [[:i, nil], [:m, nil]]
|
117
|
+
parser.parse("#Finish +o Kilroy")[:positive].should == [[:o, "Kilroy"]]
|
118
|
+
parser.parse("#Finish +v Kilroy")[:positive].should == [[:v, "Kilroy"]]
|
119
|
+
parser.parse("#Fins -s")[:negative].should == [[:s, nil]]
|
120
|
+
parser.parse("#42 +k oulu")[:positive].should == [[:k, "oulu"]]
|
121
|
+
parser.parse("#eu-opers +l 10")[:positive].should == [[:l, "10"]]
|
122
|
+
parser.parse("&oulu +b")[:positive].should == [[:b, nil]]
|
123
|
+
parser.parse("&oulu +b *!*@*")[:positive].should == [[:b, "*!*@*"]]
|
124
|
+
parser.parse("&oulu +b *!*@*.edu")[:positive].should == [[:b, "*!*@*.edu"]]
|
125
|
+
|
126
|
+
parser.parse("#oulu +e")[:positive].should == [[:e, nil]]
|
127
|
+
parser.parse("#oulu +e *!*@*.edu")[:positive].should == [[:e, "*!*@*.edu"]]
|
128
|
+
parser.parse("#oulu +I")[:positive].should == [[:I, nil]]
|
129
|
+
parser.parse("#oulu +I *!*@*.edu")[:positive].should == [[:I, "*!*@*.edu"]]
|
130
|
+
|
131
|
+
parser.parse("#foo +ooo foo bar baz").should == {
|
132
|
+
:positive => [[:o, "foo"], [:o, "bar"], [:o, "baz"]],
|
133
|
+
:negative => [],
|
134
|
+
}
|
135
|
+
parser.parse("#foo +oo-o foo bar baz").should == {
|
136
|
+
:positive => [[:o, "foo"], [:o, "bar"]],
|
137
|
+
:negative => [[:o, "baz"]],
|
138
|
+
}
|
139
|
+
parser.parse("#foo -oo+o foo bar baz").should == {
|
140
|
+
:positive => [[:o, "baz"]],
|
141
|
+
:negative => [[:o, "foo"], [:o, "bar"]],
|
142
|
+
}
|
143
|
+
parser.parse("#foo +imv foo").should == {
|
144
|
+
:positive => [[:i, nil], [:m, nil], [:v, "foo"]],
|
145
|
+
:negative => [],
|
146
|
+
}
|
147
|
+
|
148
|
+
parser.parse("#foo +lk 10 foo").should == {
|
149
|
+
:positive => [[:l, "10"], [:k, "foo"]],
|
150
|
+
:negative => [],
|
151
|
+
}
|
152
|
+
parser.parse("#foo -l+k foo").should == {
|
153
|
+
:positive => [[:k, "foo"]],
|
154
|
+
:negative => [[:l, nil]],
|
155
|
+
}
|
156
|
+
parser.parse("#foo -q+ah foo bar baz").should == {
|
157
|
+
:positive => [[:a, "bar"], [:h, "baz"]],
|
158
|
+
:negative => [[:q, "foo"]],
|
159
|
+
}
|
160
|
+
parser.parse("#foo +Av foo").should == {
|
161
|
+
:positive => [[:A, nil], [:v, "foo"]],
|
162
|
+
:negative => [],
|
163
|
+
}
|
164
|
+
end
|
165
|
+
end
|