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.
- data/ChangeLog +7 -0
- data/examples/hatena-star-stream.rb +265 -0
- data/examples/lig.rb +3 -0
- data/examples/mixi.rb +236 -0
- data/examples/tig.rb +68 -21
- data/lib/net/irc.rb +6 -811
- data/lib/net/irc/client.rb +227 -0
- data/lib/net/irc/constants.rb +214 -0
- data/lib/net/irc/message.rb +100 -0
- data/lib/net/irc/message/modeparser.rb +44 -0
- data/lib/net/irc/message/modeparser/hyperion.rb +88 -0
- data/lib/net/irc/message/modeparser/rfc1459.rb +21 -0
- data/lib/net/irc/pattern.rb +68 -0
- data/lib/net/irc/server.rb +188 -0
- data/spec/modeparser_spec.rb +43 -0
- data/spec/net-irc_spec.rb +7 -7
- metadata +17 -3
@@ -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
|