net-irc 0.0.3 → 0.0.4
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.
- 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
|