net-irc 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,44 @@
1
+ class Net::IRC::Message::ModeParser
2
+
3
+ def initialize(require_arg, definition)
4
+ @require_arg = require_arg.map {|i| i.to_sym }
5
+ @definition = definition
6
+ end
7
+
8
+ def parse(arg)
9
+ params = arg.kind_of?(Net::IRC::Message) ? arg.to_a : arg.split(/\s+/)
10
+
11
+ ret = {
12
+ :positive => [],
13
+ :negative => [],
14
+ }
15
+
16
+ current = nil, arg_pos = 0
17
+ params[1].each_byte do |c|
18
+ sym = c.chr.to_sym
19
+ case sym
20
+ when :+
21
+ current = ret[:positive]
22
+ when :-
23
+ current = ret[:negative]
24
+ else
25
+ case
26
+ when @require_arg.include?(sym)
27
+ current << [sym, params[arg_pos + 2]]
28
+ arg_pos += 1
29
+ when @definition.key?(sym)
30
+ current << [sym, nil]
31
+ else
32
+ # fallback, should raise exception
33
+ # but not for convenience
34
+ current << [sym, nil]
35
+ end
36
+ end
37
+ end
38
+
39
+ ret
40
+ end
41
+
42
+ autoload :RFC1459, "net/irc/message/modeparser/rfc1459"
43
+ autoload :Hyperion, "net/irc/message/modeparser/hyperion"
44
+ end
@@ -0,0 +1,88 @@
1
+ module Net::IRC::Message::ModeParser::Hyperion
2
+ Channel = Net::IRC::Message::ModeParser.new(%w|o l b v k q e|, {
3
+ :o => "channel ban",
4
+ :c => "color filter",
5
+ :d => "realname ban",
6
+ :e => "ban exemption",
7
+ :f => "forward on uninvited",
8
+ :g => "allow anybody to invite",
9
+ :i => "invite-only",
10
+ :I => "invite-only exemption",
11
+ :j => "jupe channel",
12
+ :J => "join throttling",
13
+ :k => "channel password",
14
+ :l => "join limit",
15
+ :L => "large ban/exempt /invex lists",
16
+ :m => "moderated",
17
+ :n => "prevent external send",
18
+ :p => "deprecated",
19
+ :P => "permanent channel",
20
+ :q => "quiet user",
21
+ :Q => "block forwarded users",
22
+ :r => "block unidentified",
23
+ :R => "quiet unidentified",
24
+ :s => "secret channel",
25
+ :t => "only ops can change topic",
26
+ :z => "reduced moderation",
27
+ })
28
+ User = Net::IRC::Message::ModeParser.new(%w||, {
29
+ :a => "user auspex",
30
+ :A => "server auspex",
31
+ :b => "see bots",
32
+ :B => "edit other people",
33
+ :c => "see client connections",
34
+ :C => "block ctcp",
35
+ :d => "debug",
36
+ :D => "die command",
37
+ :e => "identified",
38
+ :E => "reject messages from unidentified users",
39
+ :f => "full warning",
40
+ :F => "immune to flood protection",
41
+ :G => "global kill",
42
+ :h => "high priority",
43
+ :H => "rehash",
44
+ :i => "invisible",
45
+ :I => "refuse invite",
46
+ :j => "autodline",
47
+ :k => "server kill notices",
48
+ :K => "local kill",
49
+ :l => "channel creation notices",
50
+ :m => "immune",
51
+ :M => "mass notices",
52
+ :n => "nick change notices",
53
+ :N => "use any nick",
54
+ :o => "operator",
55
+ :p => "god mode",
56
+ :P => "setname and setident",
57
+ :Q => "disable forwarding",
58
+ :r => "notices on name rejections",
59
+ :R => "server routing",
60
+ :s => "server notices",
61
+ :S => "run commands remotely",
62
+ :T => "appear in STATS p",
63
+ :u => "join more channels",
64
+ :U => "unkline",
65
+ :v => "view privileges",
66
+ :V => "view routing",
67
+ :w => "receive wallops",
68
+ :W => "send wallops",
69
+ :x => "see servers joining",
70
+ :X => "experimental features",
71
+ :y => "spy",
72
+ :Y => "misc server connect notices",
73
+ :z => "receive operwall",
74
+ :Z => "send operwall",
75
+ :0 => "see opers",
76
+ :1 => "see connection settings",
77
+ :2 => "see network bans",
78
+ :3 => "see nick bans",
79
+ :4 => "see STATS T",
80
+ :5 => "see STATS ?",
81
+ :6 => "send/receive regardless of +E/noidprivmsg",
82
+ :9 => "use testline",
83
+ :* => "grant umodes",
84
+ })
85
+ end
86
+
87
+
88
+
@@ -0,0 +1,21 @@
1
+ module Net::IRC::Message::ModeParser::RFC1459
2
+ Channel = Net::IRC::Message::ModeParser.new(%w|o l b v k|, {
3
+ :o => "give/take channel operator privileges",
4
+ :p => "private channel flag",
5
+ :s => "select channel flag",
6
+ :i => "invite-only channel flag",
7
+ :t => "topic settable by channel operator only flag",
8
+ :n => "no messages to channel from clients on the outside",
9
+ :m => "moderated channel",
10
+ :l => "set the user limit to channel",
11
+ :b => "set a ban mask to keep users out",
12
+ :v => "give/take the ability to speak on a moderated channel",
13
+ :k => "set a channel key (password)",
14
+ })
15
+ User = Net::IRC::Message::ModeParser.new(%w||, {
16
+ :i => "marks a users as invisible",
17
+ :s => "marks a user for receipt of server notices",
18
+ :w => "user receives wallops",
19
+ :o => "operator flag",
20
+ })
21
+ end
@@ -0,0 +1,68 @@
1
+
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 = '\d'
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}\\w][-#{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,188 @@
1
+ class Net::IRC::Server
2
+ # Server global state for accessing Server::Session
3
+ attr_accessor :state
4
+
5
+ def initialize(host, port, session_class, opts={})
6
+ @host = host
7
+ @port = port
8
+ @session_class = session_class
9
+ @opts = OpenStruct.new(opts)
10
+ @sessions = []
11
+ @state = {}
12
+ end
13
+
14
+ # Start server loop.
15
+ def start
16
+ @serv = TCPServer.new(@host, @port)
17
+ @log = @opts.logger || Logger.new($stdout)
18
+ @log.info "Host: #{@host} Port:#{@port}"
19
+ @accept = Thread.start do
20
+ loop do
21
+ Thread.start(@serv.accept) do |s|
22
+ begin
23
+ @log.info "Client connected, new session starting..."
24
+ s = @session_class.new(self, s, @log, @opts)
25
+ @sessions << s
26
+ s.start
27
+ rescue Exception => e
28
+ puts e
29
+ puts e.backtrace
30
+ ensure
31
+ @sessions.delete(s)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ @accept.join
37
+ end
38
+
39
+ # Close all sessions.
40
+ def finish
41
+ Thread.exclusive do
42
+ @accept.kill
43
+ begin
44
+ @serv.close
45
+ rescue
46
+ end
47
+ @sessions.each do |s|
48
+ s.finish
49
+ end
50
+ end
51
+ end
52
+
53
+
54
+ class Session
55
+ include Net::IRC
56
+ include Constants
57
+
58
+ attr_reader :prefix, :nick, :real, :host
59
+
60
+ # Override subclass.
61
+ def server_name
62
+ "net-irc"
63
+ end
64
+
65
+ # Override subclass.
66
+ def server_version
67
+ "0.0.0"
68
+ end
69
+
70
+ # Override subclass.
71
+ def avaiable_user_modes
72
+ "eixwy"
73
+ end
74
+
75
+ # Override subclass.
76
+ def avaiable_channel_modes
77
+ "spknm"
78
+ end
79
+
80
+ def initialize(server, socket, logger, opts={})
81
+ @server, @socket, @log, @opts = server, socket, logger, opts
82
+ end
83
+
84
+ def self.start(*args)
85
+ new(*args).start
86
+ end
87
+
88
+ # Start session loop.
89
+ def start
90
+ on_connected
91
+ while l = @socket.gets
92
+ begin
93
+ @log.debug "RECEIVE: #{l.chomp}"
94
+ m = Message.parse(l)
95
+ next if on_message(m) === true
96
+
97
+ name = "on_#{(COMMANDS[m.command.upcase] || m.command).downcase}"
98
+ send(name, m) if respond_to?(name)
99
+
100
+ break if m.command == QUIT
101
+ rescue Message::InvalidMessage
102
+ @log.error "MessageParse: " + l.inspect
103
+ end
104
+ end
105
+ rescue IOError
106
+ ensure
107
+ finish
108
+ end
109
+
110
+ # Close this session.
111
+ def finish
112
+ begin
113
+ @socket.close
114
+ rescue
115
+ end
116
+ on_disconnected
117
+ end
118
+
119
+ # Default PASS callback.
120
+ # Set @pass.
121
+ def on_pass(m)
122
+ @pass = m.params[0]
123
+ end
124
+
125
+ # Default NICK callback.
126
+ # Set @nick.
127
+ def on_nick(m)
128
+ @nick = m.params[0]
129
+ end
130
+
131
+ # Default USER callback.
132
+ # Set @user, @real, @host and call initial_message.
133
+ def on_user(m)
134
+ @user, @real = m.params[0], m.params[3]
135
+ @host = @socket.peeraddr[2]
136
+ @prefix = Prefix.new("#{@nick}!#{@user}@#{@host}")
137
+ initial_message
138
+ end
139
+
140
+ # Call when socket connected.
141
+ def on_connected
142
+ end
143
+
144
+ # Call when socket closed.
145
+ def on_disconnected
146
+ end
147
+
148
+ # Catch all messages.
149
+ # If this method return true, aother callback will not be called.
150
+ def on_message(m)
151
+ end
152
+
153
+ # Default PING callback. Response PONG.
154
+ def on_ping(m)
155
+ post server_name, PONG, m.params[0]
156
+ end
157
+
158
+ # Do nothing.
159
+ # This is for avoiding error on calling super.
160
+ # So you can always call super at subclass.
161
+ def method_missing(name, *args)
162
+ end
163
+
164
+ private
165
+ # Post message to server.
166
+ #
167
+ # include Net::IRC::Constans
168
+ # post prefix, PRIVMSG, "#channel", "foobar"
169
+ def post(prefix, command, *params)
170
+ m = Message.new(prefix, command, params.map {|s|
171
+ s.gsub(/[\r\n]/, " ")
172
+ })
173
+ @log.debug "SEND: #{m.to_s.chomp}"
174
+ @socket << m
175
+ rescue IOError
176
+ finish
177
+ end
178
+
179
+ # Call when client connected.
180
+ # Send RPL_WELCOME sequence. If you want to customize, override this method at subclass.
181
+ def initial_message
182
+ post server_name, RPL_WELCOME, @nick, "Welcome to the Internet Relay Network #{@prefix}"
183
+ post server_name, RPL_YOURHOST, @nick, "Your host is #{server_name}, running version #{server_version}"
184
+ post server_name, RPL_CREATED, @nick, "This server was created #{Time.now}"
185
+ post server_name, RPL_MYINFO, @nick, "#{server_name} #{server_version} #{avaiable_user_modes} #{avaiable_channel_modes}"
186
+ end
187
+ end
188
+ end # Server
@@ -0,0 +1,43 @@
1
+
2
+ $LOAD_PATH << "lib"
3
+ $LOAD_PATH << "../lib"
4
+
5
+ require "net/irc"
6
+ include Net::IRC
7
+ include Constants
8
+
9
+
10
+ describe Message::ModeParser do
11
+ it "should parse RFC1459 correctly" do
12
+ Message::ModeParser::RFC1459::Channel.parse("#Finish +im")[:positive].should == [[:i, nil], [:m, nil]]
13
+ Message::ModeParser::RFC1459::Channel.parse("#Finish +o Kilroy")[:positive].should == [[:o, "Kilroy"]]
14
+ Message::ModeParser::RFC1459::Channel.parse("#Finish +v Kilroy")[:positive].should == [[:v, "Kilroy"]]
15
+ Message::ModeParser::RFC1459::Channel.parse("#Fins -s")[:negative].should == [[:s, nil]]
16
+ Message::ModeParser::RFC1459::Channel.parse("#42 +k oulu")[:positive].should == [[:k, "oulu"]]
17
+ Message::ModeParser::RFC1459::Channel.parse("#eu-opers +l 10")[:positive].should == [[:l, "10"]]
18
+ Message::ModeParser::RFC1459::Channel.parse("&oulu +b")[:positive].should == [[:b, nil]]
19
+ Message::ModeParser::RFC1459::Channel.parse("&oulu +b *!*@*")[:positive].should == [[:b, "*!*@*"]]
20
+ Message::ModeParser::RFC1459::Channel.parse("&oulu +b *!*@*.edu")[:positive].should == [[:b, "*!*@*.edu"]]
21
+
22
+ Message::ModeParser::RFC1459::Channel.parse("#foo +ooo foo bar baz").should == {
23
+ :positive => [[:o, "foo"], [:o, "bar"], [:o, "baz"]],
24
+ :negative => [],
25
+ }
26
+ Message::ModeParser::RFC1459::Channel.parse("#foo +oo-o foo bar baz").should == {
27
+ :positive => [[:o, "foo"], [:o, "bar"]],
28
+ :negative => [[:o, "baz"]],
29
+ }
30
+ Message::ModeParser::RFC1459::Channel.parse("#foo -oo+o foo bar baz").should == {
31
+ :positive => [[:o, "baz"]],
32
+ :negative => [[:o, "foo"], [:o, "bar"]],
33
+ }
34
+ Message::ModeParser::RFC1459::Channel.parse("#foo +imv foo").should == {
35
+ :positive => [[:i, nil], [:m, nil], [:v, "foo"]],
36
+ :negative => [],
37
+ }
38
+
39
+ Message::ModeParser::RFC1459::User.parse("WIZ -w")[:negative].should == [[:w, nil]]
40
+ Message::ModeParser::RFC1459::User.parse("ANGEL +i")[:positive].should == [[:i, nil]]
41
+ Message::ModeParser::RFC1459::User.parse("WIZ -o")[:negative].should == [[:o, nil]]
42
+ end
43
+ end