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,227 @@
1
+ class Net::IRC::Client
2
+ include Net::IRC
3
+ include Constants
4
+
5
+ attr_reader :host, :port, :opts
6
+ attr_reader :prefix, :channels
7
+
8
+ def initialize(host, port, opts={})
9
+ @host = host
10
+ @port = port
11
+ @opts = OpenStruct.new(opts)
12
+ @log = @opts.logger || Logger.new($stdout)
13
+ @channels = {
14
+ # "#channel" => {
15
+ # :modes => [],
16
+ # :users => [],
17
+ # }
18
+ }
19
+ @channels.extend(MonitorMixin)
20
+ end
21
+
22
+ # Connect to server and start loop.
23
+ def start
24
+ @socket = TCPSocket.open(@host, @port)
25
+ on_connected
26
+ post PASS, @opts.pass if @opts.pass
27
+ post NICK, @opts.nick
28
+ post USER, @opts.user, "0", "*", @opts.real
29
+ while l = @socket.gets
30
+ begin
31
+ @log.debug "RECEIVE: #{l.chomp}"
32
+ m = Message.parse(l)
33
+ next if on_message(m) === true
34
+ name = "on_#{(COMMANDS[m.command.upcase] || m.command).downcase}"
35
+ send(name, m) if respond_to?(name)
36
+ rescue Exception => e
37
+ warn e
38
+ warn e.backtrace.join("\r\t")
39
+ raise
40
+ rescue Message::InvalidMessage
41
+ @log.error "MessageParse: " + l.inspect
42
+ end
43
+ end
44
+ rescue IOError
45
+ ensure
46
+ finish
47
+ end
48
+
49
+ # Close connection to server.
50
+ def finish
51
+ begin
52
+ @socket.close
53
+ rescue
54
+ end
55
+ on_disconnected
56
+ end
57
+
58
+ # Catch all messages.
59
+ # If this method return true, aother callback will not be called.
60
+ def on_message(m)
61
+ end
62
+
63
+ # Default RPL_WELCOME callback.
64
+ # This sets @prefix from the message.
65
+ def on_rpl_welcome(m)
66
+ @prefix = Prefix.new(m[1][/\S+$/])
67
+ end
68
+
69
+ # Default PING callback. Response PONG.
70
+ def on_ping(m)
71
+ post PONG, @prefix ? @prefix.nick : ""
72
+ end
73
+
74
+ # For managing channel
75
+ def on_rpl_namreply(m)
76
+ type = m[1]
77
+ channel = m[2]
78
+ init_channel(channel)
79
+
80
+ @channels.synchronize do
81
+ m[3].split(/\s+/).each do |u|
82
+ _, mode, nick = *u.match(/^([@+]?)(.+)/)
83
+
84
+ @channels[channel][:users] << nick
85
+ @channels[channel][:users].uniq!
86
+
87
+ case mode
88
+ when "@" # channel operator
89
+ @channels[channel][:modes] << [:o, nick]
90
+ when "+" # voiced (under moderating mode)
91
+ @channels[channel][:modes] << [:v, nick]
92
+ end
93
+ end
94
+
95
+ case type
96
+ when "@" # secret
97
+ @channels[channel][:modes] << [:s, nil]
98
+ when "*" # private
99
+ @channels[channel][:modes] << [:p, nil]
100
+ when "=" # public
101
+ end
102
+
103
+ @channels[channel][:modes].uniq!
104
+ end
105
+ end
106
+
107
+ # For managing channel
108
+ def on_part(m)
109
+ nick = m.prefix.nick
110
+ channel = m[0]
111
+ init_channel(channel)
112
+
113
+ @channels.synchronize do
114
+ info = @channels[channel]
115
+ if info
116
+ info[:users].delete(nick)
117
+ info[:modes].delete_if {|u|
118
+ u[1] == nick
119
+ }
120
+ end
121
+ end
122
+ end
123
+
124
+ # For managing channel
125
+ def on_quit(m)
126
+ nick = m.prefix.nick
127
+
128
+ @channels.synchronize do
129
+ @channels.each do |channel, info|
130
+ info[:users].delete(nick)
131
+ info[:modes].delete_if {|u|
132
+ u[1] == nick
133
+ }
134
+ end
135
+ end
136
+ end
137
+
138
+ # For managing channel
139
+ def on_kick(m)
140
+ users = m[1].split(/,/)
141
+
142
+ @channels.synchronize do
143
+ m[0].split(/,/).each do |chan|
144
+ init_channel(chan)
145
+ info = @channels[chan]
146
+ if info
147
+ users.each do |nick|
148
+ info[:users].delete(nick)
149
+ info[:modes].delete_if {|u|
150
+ u[1] == nick
151
+ }
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
157
+
158
+ # For managing channel
159
+ def on_join(m)
160
+ nick = m.prefix.nick
161
+ channel = m[0]
162
+
163
+ @channels.synchronize do
164
+ init_channel(channel)
165
+
166
+ @channels[channel][:users] << nick
167
+ @channels[channel][:users].uniq!
168
+ end
169
+ end
170
+
171
+ # For managing channel
172
+ def on_mode(m)
173
+ channel = m[0]
174
+ @channels.synchronize do
175
+ init_channel(channel)
176
+
177
+ mode = Message::ModeParser::RFC1459::Channel.parse(m)
178
+ mode[:negative].each do |m|
179
+ @channels[channel][:modes].delete(m)
180
+ end
181
+
182
+ mode[:positive].each do |m|
183
+ @channels[channel][:modes] << m
184
+ end
185
+
186
+ @channels[channel][:modes].uniq!
187
+ [mode[:negative], mode[:positive]]
188
+ end
189
+ end
190
+
191
+ # For managing channel
192
+ def init_channel(channel)
193
+ @channels[channel] ||= {
194
+ :modes => [],
195
+ :users => [],
196
+ }
197
+ end
198
+
199
+ # Do nothing.
200
+ # This is for avoiding error on calling super.
201
+ # So you can always call super at subclass.
202
+ def method_missing(name, *args)
203
+ end
204
+
205
+ # Call when socket connected.
206
+ def on_connected
207
+ end
208
+
209
+ # Call when socket closed.
210
+ def on_disconnected
211
+ end
212
+
213
+ private
214
+
215
+ # Post message to server.
216
+ #
217
+ # include Net::IRC::Constants
218
+ # post PRIVMSG, "#channel", "foobar"
219
+ def post(command, *params)
220
+ m = Message.new(nil, command, params.map {|s|
221
+ s ? s.gsub(/[\r\n]/, " ") : ""
222
+ })
223
+
224
+ @log.debug "SEND: #{m.to_s.chomp}"
225
+ @socket << m
226
+ end
227
+ end # Client
@@ -0,0 +1,214 @@
1
+ module Net::IRC::Constants # :nodoc:
2
+ RPL_WELCOME = '001'
3
+ RPL_YOURHOST = '002'
4
+ RPL_CREATED = '003'
5
+ RPL_MYINFO = '004'
6
+ RPL_BOUNCE = '005'
7
+ RPL_USERHOST = '302'
8
+ RPL_ISON = '303'
9
+ RPL_AWAY = '301'
10
+ RPL_UNAWAY = '305'
11
+ RPL_NOWAWAY = '306'
12
+ RPL_WHOISUSER = '311'
13
+ RPL_WHOISSERVER = '312'
14
+ RPL_WHOISOPERATOR = '313'
15
+ RPL_WHOISIDLE = '317'
16
+ RPL_ENDOFWHOIS = '318'
17
+ RPL_WHOISCHANNELS = '319'
18
+ RPL_WHOWASUSER = '314'
19
+ RPL_ENDOFWHOWAS = '369'
20
+ RPL_LISTSTART = '321'
21
+ RPL_LIST = '322'
22
+ RPL_LISTEND = '323'
23
+ RPL_UNIQOPIS = '325'
24
+ RPL_CHANNELMODEIS = '324'
25
+ RPL_NOTOPIC = '331'
26
+ RPL_TOPIC = '332'
27
+ RPL_INVITING = '341'
28
+ RPL_SUMMONING = '342'
29
+ RPL_INVITELIST = '346'
30
+ RPL_ENDOFINVITELIST = '347'
31
+ RPL_EXCEPTLIST = '348'
32
+ RPL_ENDOFEXCEPTLIST = '349'
33
+ RPL_VERSION = '351'
34
+ RPL_WHOREPLY = '352'
35
+ RPL_ENDOFWHO = '315'
36
+ RPL_NAMREPLY = '353'
37
+ RPL_ENDOFNAMES = '366'
38
+ RPL_LINKS = '364'
39
+ RPL_ENDOFLINKS = '365'
40
+ RPL_BANLIST = '367'
41
+ RPL_ENDOFBANLIST = '368'
42
+ RPL_INFO = '371'
43
+ RPL_ENDOFINFO = '374'
44
+ RPL_MOTDSTART = '375'
45
+ RPL_MOTD = '372'
46
+ RPL_ENDOFMOTD = '376'
47
+ RPL_YOUREOPER = '381'
48
+ RPL_REHASHING = '382'
49
+ RPL_YOURESERVICE = '383'
50
+ RPL_TIME = '391'
51
+ RPL_USERSSTART = '392'
52
+ RPL_USERS = '393'
53
+ RPL_ENDOFUSERS = '394'
54
+ RPL_NOUSERS = '395'
55
+ RPL_TRACELINK = '200'
56
+ RPL_TRACECONNECTING = '201'
57
+ RPL_TRACEHANDSHAKE = '202'
58
+ RPL_TRACEUNKNOWN = '203'
59
+ RPL_TRACEOPERATOR = '204'
60
+ RPL_TRACEUSER = '205'
61
+ RPL_TRACESERVER = '206'
62
+ RPL_TRACESERVICE = '207'
63
+ RPL_TRACENEWTYPE = '208'
64
+ RPL_TRACECLASS = '209'
65
+ RPL_TRACERECONNECT = '210'
66
+ RPL_TRACELOG = '261'
67
+ RPL_TRACEEND = '262'
68
+ RPL_STATSLINKINFO = '211'
69
+ RPL_STATSCOMMANDS = '212'
70
+ RPL_ENDOFSTATS = '219'
71
+ RPL_STATSUPTIME = '242'
72
+ RPL_STATSOLINE = '243'
73
+ RPL_UMODEIS = '221'
74
+ RPL_SERVLIST = '234'
75
+ RPL_SERVLISTEND = '235'
76
+ RPL_LUSERCLIENT = '251'
77
+ RPL_LUSEROP = '252'
78
+ RPL_LUSERUNKNOWN = '253'
79
+ RPL_LUSERCHANNELS = '254'
80
+ RPL_LUSERME = '255'
81
+ RPL_ADMINME = '256'
82
+ RPL_ADMINLOC1 = '257'
83
+ RPL_ADMINLOC2 = '258'
84
+ RPL_ADMINEMAIL = '259'
85
+ RPL_TRYAGAIN = '263'
86
+ ERR_NOSUCHNICK = '401'
87
+ ERR_NOSUCHSERVER = '402'
88
+ ERR_NOSUCHCHANNEL = '403'
89
+ ERR_CANNOTSENDTOCHAN = '404'
90
+ ERR_TOOMANYCHANNELS = '405'
91
+ ERR_WASNOSUCHNICK = '406'
92
+ ERR_TOOMANYTARGETS = '407'
93
+ ERR_NOSUCHSERVICE = '408'
94
+ ERR_NOORIGIN = '409'
95
+ ERR_NORECIPIENT = '411'
96
+ ERR_NOTEXTTOSEND = '412'
97
+ ERR_NOTOPLEVEL = '413'
98
+ ERR_WILDTOPLEVEL = '414'
99
+ ERR_BADMASK = '415'
100
+ ERR_UNKNOWNCOMMAND = '421'
101
+ ERR_NOMOTD = '422'
102
+ ERR_NOADMININFO = '423'
103
+ ERR_FILEERROR = '424'
104
+ ERR_NONICKNAMEGIVEN = '431'
105
+ ERR_ERRONEUSNICKNAME = '432'
106
+ ERR_NICKNAMEINUSE = '433'
107
+ ERR_NICKCOLLISION = '436'
108
+ ERR_UNAVAILRESOURCE = '437'
109
+ ERR_USERNOTINCHANNEL = '441'
110
+ ERR_NOTONCHANNEL = '442'
111
+ ERR_USERONCHANNEL = '443'
112
+ ERR_NOLOGIN = '444'
113
+ ERR_SUMMONDISABLED = '445'
114
+ ERR_USERSDISABLED = '446'
115
+ ERR_NOTREGISTERED = '451'
116
+ ERR_NEEDMOREPARAMS = '461'
117
+ ERR_ALREADYREGISTRED = '462'
118
+ ERR_NOPERMFORHOST = '463'
119
+ ERR_PASSWDMISMATCH = '464'
120
+ ERR_YOUREBANNEDCREEP = '465'
121
+ ERR_YOUWILLBEBANNED = '466'
122
+ ERR_KEYSET = '467'
123
+ ERR_CHANNELISFULL = '471'
124
+ ERR_UNKNOWNMODE = '472'
125
+ ERR_INVITEONLYCHAN = '473'
126
+ ERR_BANNEDFROMCHAN = '474'
127
+ ERR_BADCHANNELKEY = '475'
128
+ ERR_BADCHANMASK = '476'
129
+ ERR_NOCHANMODES = '477'
130
+ ERR_BANLISTFULL = '478'
131
+ ERR_NOPRIVILEGES = '481'
132
+ ERR_CHANOPRIVSNEEDED = '482'
133
+ ERR_CANTKILLSERVER = '483'
134
+ ERR_RESTRICTED = '484'
135
+ ERR_UNIQOPPRIVSNEEDED = '485'
136
+ ERR_NOOPERHOST = '491'
137
+ ERR_UMODEUNKNOWNFLAG = '501'
138
+ ERR_USERSDONTMATCH = '502'
139
+ RPL_SERVICEINFO = '231'
140
+ RPL_ENDOFSERVICES = '232'
141
+ RPL_SERVICE = '233'
142
+ RPL_NONE = '300'
143
+ RPL_WHOISCHANOP = '316'
144
+ RPL_KILLDONE = '361'
145
+ RPL_CLOSING = '362'
146
+ RPL_CLOSEEND = '363'
147
+ RPL_INFOSTART = '373'
148
+ RPL_MYPORTIS = '384'
149
+ RPL_STATSCLINE = '213'
150
+ RPL_STATSNLINE = '214'
151
+ RPL_STATSILINE = '215'
152
+ RPL_STATSKLINE = '216'
153
+ RPL_STATSQLINE = '217'
154
+ RPL_STATSYLINE = '218'
155
+ RPL_STATSVLINE = '240'
156
+ RPL_STATSLLINE = '241'
157
+ RPL_STATSHLINE = '244'
158
+ RPL_STATSSLINE = '244'
159
+ RPL_STATSPING = '246'
160
+ RPL_STATSBLINE = '247'
161
+ RPL_STATSDLINE = '250'
162
+ ERR_NOSERVICEHOST = '492'
163
+
164
+ PASS = 'PASS'
165
+ NICK = 'NICK'
166
+ USER = 'USER'
167
+ OPER = 'OPER'
168
+ MODE = 'MODE'
169
+ SERVICE = 'SERVICE'
170
+ QUIT = 'QUIT'
171
+ SQUIT = 'SQUIT'
172
+ JOIN = 'JOIN'
173
+ PART = 'PART'
174
+ TOPIC = 'TOPIC'
175
+ NAMES = 'NAMES'
176
+ LIST = 'LIST'
177
+ INVITE = 'INVITE'
178
+ KICK = 'KICK'
179
+ PRIVMSG = 'PRIVMSG'
180
+ NOTICE = 'NOTICE'
181
+ MOTD = 'MOTD'
182
+ LUSERS = 'LUSERS'
183
+ VERSION = 'VERSION'
184
+ STATS = 'STATS'
185
+ LINKS = 'LINKS'
186
+ TIME = 'TIME'
187
+ CONNECT = 'CONNECT'
188
+ TRACE = 'TRACE'
189
+ ADMIN = 'ADMIN'
190
+ INFO = 'INFO'
191
+ SERVLIST = 'SERVLIST'
192
+ SQUERY = 'SQUERY'
193
+ WHO = 'WHO'
194
+ WHOIS = 'WHOIS'
195
+ WHOWAS = 'WHOWAS'
196
+ KILL = 'KILL'
197
+ PING = 'PING'
198
+ PONG = 'PONG'
199
+ ERROR = 'ERROR'
200
+ AWAY = 'AWAY'
201
+ REHASH = 'REHASH'
202
+ DIE = 'DIE'
203
+ RESTART = 'RESTART'
204
+ SUMMON = 'SUMMON'
205
+ USERS = 'USERS'
206
+ WALLOPS = 'WALLOPS'
207
+ USERHOST = 'USERHOST'
208
+ ISON = 'ISON'
209
+ end
210
+
211
+ Net::IRC::COMMANDS = Net::IRC::Constants.constants.inject({}) {|r,i| # :nodoc:
212
+ r.update(Net::IRC::Constants.const_get(i) => i)
213
+ }
214
+
@@ -0,0 +1,100 @@
1
+ class Net::IRC::Message
2
+ include Net::IRC
3
+
4
+ class InvalidMessage < Net::IRC::IRCException; end
5
+
6
+ attr_reader :prefix, :command, :params
7
+
8
+ # Parse string and return new Message.
9
+ # If the string is invalid message, this method raises Net::IRC::Message::InvalidMessage.
10
+ def self.parse(str)
11
+ _, prefix, command, *rest = *PATTERN::MESSAGE_PATTERN.match(str)
12
+ raise InvalidMessage, "Invalid message: #{str.dump}" unless _
13
+
14
+ case
15
+ when rest[0] && !rest[0].empty?
16
+ middle, trailer, = *rest
17
+ when rest[2] && !rest[2].empty?
18
+ middle, trailer, = *rest[2, 2]
19
+ when rest[1]
20
+ params = []
21
+ trailer = rest[1]
22
+ when rest[3]
23
+ params = []
24
+ trailer = rest[3]
25
+ else
26
+ params = []
27
+ end
28
+
29
+ params ||= middle.split(/ /)[1..-1]
30
+ params << trailer if trailer
31
+
32
+ new(prefix, command, params)
33
+ end
34
+
35
+ def initialize(prefix, command, params)
36
+ @prefix = Prefix.new(prefix.to_s)
37
+ @command = command
38
+ @params = params
39
+ end
40
+
41
+ # Same as @params[n].
42
+ def [](n)
43
+ @params[n]
44
+ end
45
+
46
+ # Iterate params.
47
+ def each(&block)
48
+ @params.each(&block)
49
+ end
50
+
51
+ # Stringfy message to raw IRC message.
52
+ def to_s
53
+ str = ""
54
+
55
+ str << ":#{@prefix} " unless @prefix.empty?
56
+ str << @command
57
+
58
+ if @params
59
+ f = false
60
+ @params.each do |param|
61
+ str << " "
62
+ if !f && (param.size == 0 || / / =~ param || /^:/ =~ param)
63
+ str << ":#{param}"
64
+ f = true
65
+ else
66
+ str << param
67
+ end
68
+ end
69
+ end
70
+
71
+ str << "\x0D\x0A"
72
+
73
+ str
74
+ end
75
+ alias to_str to_s
76
+
77
+ # Same as params.
78
+ def to_a
79
+ @params
80
+ end
81
+
82
+ # If the message is CTCP, return true.
83
+ def ctcp?
84
+ message = @params[1]
85
+ message[0] == 1 && message[message.length-1] == 1
86
+ end
87
+
88
+ def inspect
89
+ '#<%s:0x%x prefix:%s command:%s params:%s>' % [
90
+ self.class,
91
+ self.object_id,
92
+ @prefix,
93
+ @command,
94
+ @params.inspect
95
+ ]
96
+ end
97
+
98
+ autoload :ModeParser, "net/irc/message/modeparser"
99
+ end # Message
100
+