net-irc2 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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