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/examples/tig.rb CHANGED
@@ -23,7 +23,7 @@ Configuration example for Tiarra ( http://coderepos.org/share/wiki/Tiarra ).
23
23
  twitter {
24
24
  host: localhost
25
25
  port: 16668
26
- name: username@example.com athack jabber=username@example.com:jabberpasswd
26
+ name: username@example.com athack jabber=username@example.com:jabberpasswd tid=10
27
27
  password: password on Twitter
28
28
  in-encoding: utf8
29
29
  out-encoding: utf8
@@ -40,6 +40,32 @@ it's good for Twitter like reply command (@nick).
40
40
  In this case, you will see torrent of join messages after connected,
41
41
  because NAMES list can't send @ leading nick (it interpreted op.)
42
42
 
43
+ ### tid=<color>
44
+
45
+ Apply id to each message for make favorites by CTCP ACTION.
46
+
47
+ /me fav id
48
+
49
+ <color> can be
50
+
51
+ 0 => white
52
+ 1 => black
53
+ 2 => blue navy
54
+ 3 => green
55
+ 4 => red
56
+ 5 => brown maroon
57
+ 6 => purple
58
+ 7 => orange olive
59
+ 8 => yellow
60
+ 9 => lightgreen lime
61
+ 10 => teal
62
+ 11 => lightcyan cyan aqua
63
+ 12 => lightblue royal
64
+ 13 => pink lightpurple fuchsia
65
+ 14 => grey
66
+ 15 => lightgrey silver
67
+
68
+
43
69
  ### jabber=<jid>:<pass>
44
70
 
45
71
  If `jabber=<jid>:<pass>` option specified,
@@ -52,6 +78,10 @@ install 'xmpp4r-simple' gem.
52
78
 
53
79
  Be careful for managing password.
54
80
 
81
+ ### alwaysim
82
+
83
+ Use IM instead of any APIs (ex. post)
84
+
55
85
 
56
86
  ## Licence
57
87
 
@@ -122,13 +152,15 @@ class TwitterIrcGateway < Net::IRC::Server::Session
122
152
  post server_name, MODE, main_channel, "+o", @prefix.nick
123
153
 
124
154
  @real, *@opts = @opts.name || @real.split(/\s+/)
125
- @opts ||= []
155
+ @opts = @opts.inject({}) {|r,i|
156
+ key, value = i.split(/=/)
157
+ r.update(key => value)
158
+ }
126
159
  @tmap = TypableMap.new
127
160
 
128
- jabber = @opts.find {|i| i =~ /^jabber=(\S+?):(\S+)/ }
129
- if jabber
130
- jid, pass = Regexp.last_match.captures
131
- jabber.replace("jabber=#{jid}:********")
161
+ if @opts["jabber"]
162
+ jid, pass = @opts["jabber"].split(/:/, 2)
163
+ @opts["jabber"].replace("jabber=#{jid}:********")
132
164
  if jabber_bot_id
133
165
  begin
134
166
  require "xmpp4r-simple"
@@ -139,11 +171,12 @@ class TwitterIrcGateway < Net::IRC::Server::Session
139
171
  finish
140
172
  end
141
173
  else
142
- jabber = nil
174
+ @opts.delete("jabber")
143
175
  log "This gateway does not support Jabber bot."
144
176
  end
145
177
  end
146
178
 
179
+ log "Client Options: #{@opts.inspect}"
147
180
  @log.info "Client Options: #{@opts.inspect}"
148
181
 
149
182
  @timeline = []
@@ -164,7 +197,7 @@ class TwitterIrcGateway < Net::IRC::Server::Session
164
197
  end
165
198
  sleep 3
166
199
 
167
- return if jabber
200
+ return if @opts["jabber"]
168
201
 
169
202
  @check_timeline_thread = Thread.start do
170
203
  loop do
@@ -198,7 +231,7 @@ class TwitterIrcGateway < Net::IRC::Server::Session
198
231
  target, message = *m.params
199
232
  begin
200
233
  if target =~ /^#/
201
- if @im && @im.connected? # in jabber mode, using jabber post
234
+ if @opts.key?("alwaysim") && @im && @im.connected? # in jabber mode, using jabber post
202
235
  ret = @im.deliver(jabber_bot_id, message)
203
236
  post "#{nick}!#{nick}@#{api_base.host}", TOPIC, main_channel, untinyurl(message)
204
237
  else
@@ -234,17 +267,27 @@ class TwitterIrcGateway < Net::IRC::Server::Session
234
267
  end
235
268
 
236
269
  unless res
237
- post nil, ERR_NOSUCHNICK, nick, "No such nick/channel"
270
+ post nil, ERR_NOSUCHNICK, nick, "No such nick/channel"
238
271
  end
239
272
  when "fav"
240
273
  tid = args[0]
241
- id = @tmap[tid]
242
- if id
274
+ st = @tmap[tid]
275
+ if st
276
+ id = st["id"] || st["rid"]
243
277
  res = api("favorites/create/#{id}", {})
244
278
  post nil, NOTICE, main_channel, "Fav: #{res["screen_name"]}: #{res["text"]}"
245
279
  else
246
280
  post nil, NOTICE, main_channel, "No such id #{tid}"
247
281
  end
282
+ when "link"
283
+ tid = args[0]
284
+ st = @tmap[tid]
285
+ if st
286
+ st["link"] = (api_base + "/#{st["user"]["screen_name"]}/statuses/#{st["id"]}").to_s unless st["link"]
287
+ post nil, NOTICE, main_channel, st["link"]
288
+ else
289
+ post nil, NOTICE, main_channel, "No such id #{tid}"
290
+ end
248
291
  end
249
292
  rescue ApiFailed => e
250
293
  log e.inspect
@@ -349,13 +392,17 @@ class TwitterIrcGateway < Net::IRC::Server::Session
349
392
  nick = s["user_login_id"] || s["user"]["screen_name"] # it may be better to use user_login_id in Wassr
350
393
  mesg = generate_status_message(s)
351
394
 
352
- tid = @tmap.push(id)
395
+ tid = @tmap.push(s)
353
396
 
354
397
  @log.debug [id, nick, mesg]
355
398
  if nick == @nick # 自分のときは topic に
356
399
  post "#{nick}!#{nick}@#{api_base.host}", TOPIC, main_channel, untinyurl(mesg)
357
400
  else
358
- message(nick, main_channel, "%s [%s]" % [mesg, tid])
401
+ if @opts["tid"]
402
+ message(nick, main_channel, "%s \x03%s [%s]" % [mesg, @opts["tid"], tid])
403
+ else
404
+ message(nick, main_channel, "%s" % [mesg, tid])
405
+ end
359
406
  end
360
407
  @groups.each do |channel, members|
361
408
  if members.include?(nick)
@@ -409,7 +456,7 @@ class TwitterIrcGateway < Net::IRC::Server::Session
409
456
  first = true unless @friends
410
457
  @friends ||= []
411
458
  friends = api("statuses/friends")
412
- if first && !@opts.include?("athack")
459
+ if first && !@opts.key?("athack")
413
460
  @friends = friends
414
461
  post nil, RPL_NAMREPLY, @nick, "=", main_channel, @friends.map{|i| "@#{i["screen_name"]}" }.join(" ")
415
462
  post nil, RPL_ENDOFNAMES, @nick, main_channel, "End of NAMES list"
@@ -421,11 +468,11 @@ class TwitterIrcGateway < Net::IRC::Server::Session
421
468
  return if !first && (now_friends.length - prv_friends.length).abs > 10
422
469
 
423
470
  (now_friends - prv_friends).each do |join|
424
- join = "@#{join}" if @opts.include?("athack")
471
+ join = "@#{join}" if @opts.key?("athack")
425
472
  post "#{join}!#{join}@#{api_base.host}", JOIN, main_channel
426
473
  end
427
474
  (prv_friends - now_friends).each do |part|
428
- part = "@#{part}" if @opts.include?("athack")
475
+ part = "@#{part}" if @opts.key?("athack")
429
476
  post "#{part}!#{part}@#{api_base.host}", PART, main_channel, ""
430
477
  end
431
478
  @friends = friends
@@ -487,9 +534,9 @@ class TwitterIrcGateway < Net::IRC::Server::Session
487
534
  header = {
488
535
  "User-Agent" => @user_agent,
489
536
  "Authorization" => "Basic " + ["#{@real}:#{@pass}"].pack("m"),
490
- "X-Twitter-Client" => api_source,
491
- "X-Twitter-Client-Version" => server_version,
492
- "X-Twitter-Client-URL" => "http://coderepos.org/share/browser/lang/ruby/misc/tig.rb",
537
+ # "X-Twitter-Client" => api_source,
538
+ # "X-Twitter-Client-Version" => server_version,
539
+ # "X-Twitter-Client-URL" => "http://coderepos.org/share/browser/lang/ruby/misc/tig.rb",
493
540
  }
494
541
  header["If-Modified-Since"] = q["since"] if q.key?("since")
495
542
 
@@ -566,7 +613,7 @@ class TwitterIrcGateway < Net::IRC::Server::Session
566
613
  %w|a i u e o|.map {|v| "#{k}#{v}" }
567
614
  }.flatten
568
615
 
569
- def initialize(size=2)
616
+ def initialize(size=1)
570
617
  @seq = Roma
571
618
  @map = {}
572
619
  @n = 0
data/lib/net/irc.rb CHANGED
@@ -9,290 +9,15 @@ require "monitor"
9
9
  module Net; end
10
10
 
11
11
  module Net::IRC
12
- VERSION = "0.0.3"
12
+ VERSION = "0.0.4"
13
13
  class IRCException < StandardError; end
14
14
 
15
- module PATTERN # :nodoc:
16
- # letter = %x41-5A / %x61-7A ; A-Z / a-z
17
- # digit = %x30-39 ; 0-9
18
- # hexdigit = digit / "A" / "B" / "C" / "D" / "E" / "F"
19
- # special = %x5B-60 / %x7B-7D
20
- # ; "[", "]", "\", "`", "_", "^", "{", "|", "}"
21
- LETTER = 'A-Za-z'
22
- DIGIT = '\d'
23
- HEXDIGIT = "#{DIGIT}A-Fa-f"
24
- SPECIAL = '\x5B-\x60\x7B-\x7D'
15
+ require "net/irc/constants"
16
+ require "net/irc/pattern"
25
17
 
26
- # shortname = ( letter / digit ) *( letter / digit / "-" )
27
- # *( letter / digit )
28
- # ; as specified in RFC 1123 [HNAME]
29
- # hostname = shortname *( "." shortname )
30
- SHORTNAME = "[#{LETTER}#{DIGIT}](?:[-#{LETTER}#{DIGIT}]*[#{LETTER}#{DIGIT}])?"
31
- HOSTNAME = "#{SHORTNAME}(?:\\.#{SHORTNAME})*"
32
-
33
- # servername = hostname
34
- SERVERNAME = HOSTNAME
35
-
36
- # nickname = ( letter / special ) *8( letter / digit / special / "-" )
37
- #NICKNAME = "[#{LETTER}#{SPECIAL}\\w][-#{LETTER}#{DIGIT}#{SPECIAL}]*"
38
- NICKNAME = "\\S+" # for multibytes
39
-
40
- # user = 1*( %x01-09 / %x0B-0C / %x0E-1F / %x21-3F / %x41-FF )
41
- # ; any octet except NUL, CR, LF, " " and "@"
42
- USER = '[\x01-\x09\x0B-\x0C\x0E-\x1F\x21-\x3F\x41-\xFF]+'
43
-
44
- # ip4addr = 1*3digit "." 1*3digit "." 1*3digit "." 1*3digit
45
- IP4ADDR = "[#{DIGIT}]{1,3}(?:\\.[#{DIGIT}]{1,3}){3}"
46
- # ip6addr = 1*hexdigit 7( ":" 1*hexdigit )
47
- # ip6addr =/ "0:0:0:0:0:" ( "0" / "FFFF" ) ":" ip4addr
48
- IP6ADDR = "(?:[#{HEXDIGIT}]+(?::[#{HEXDIGIT}]+){7}|0:0:0:0:0:(?:0|FFFF):#{IP4ADDR})"
49
- # hostaddr = ip4addr / ip6addr
50
- HOSTADDR = "(?:#{IP4ADDR}|#{IP6ADDR})"
51
-
52
- # host = hostname / hostaddr
53
- HOST = "(?:#{HOSTNAME}|#{HOSTADDR})"
54
-
55
- # prefix = servername / ( nickname [ [ "!" user ] "@" host ] )
56
- PREFIX = "(?:#{NICKNAME}(?:(?:!#{USER})?@#{HOST})?|#{SERVERNAME})"
57
-
58
- # nospcrlfcl = %x01-09 / %x0B-0C / %x0E-1F / %x21-39 / %x3B-FF
59
- # ; any octet except NUL, CR, LF, " " and ":"
60
- NOSPCRLFCL = '\x01-\x09\x0B-\x0C\x0E-\x1F\x21-\x39\x3B-\xFF'
61
-
62
- # command = 1*letter / 3digit
63
- COMMAND = "(?:[#{LETTER}]+|[#{DIGIT}]{3})"
64
-
65
- # SPACE = %x20 ; space character
66
- # middle = nospcrlfcl *( ":" / nospcrlfcl )
67
- # trailing = *( ":" / " " / nospcrlfcl )
68
- # params = *14( SPACE middle ) [ SPACE ":" trailing ]
69
- # =/ 14( SPACE middle ) [ SPACE [ ":" ] trailing ]
70
- MIDDLE = "[#{NOSPCRLFCL}][:#{NOSPCRLFCL}]*"
71
- TRAILING = "[: #{NOSPCRLFCL}]*"
72
- PARAMS = "(?:((?: #{MIDDLE}){0,14})(?: :(#{TRAILING}))?|((?: #{MIDDLE}){14})(?::?)?(#{TRAILING}))"
73
-
74
- # crlf = %x0D %x0A ; "carriage return" "linefeed"
75
- # message = [ ":" prefix SPACE ] command [ params ] crlf
76
- CRLF = '\x0D\x0A'
77
- MESSAGE = "(?::(#{PREFIX}) )?(#{COMMAND})#{PARAMS}\s*#{CRLF}"
78
-
79
- CLIENT_PATTERN = /\A#{NICKNAME}(?:(?:!#{USER})?@#{HOST})\z/on
80
- MESSAGE_PATTERN = /\A#{MESSAGE}\z/on
81
- end # PATTERN
82
-
83
- module Constants # :nodoc:
84
- RPL_WELCOME = '001'
85
- RPL_YOURHOST = '002'
86
- RPL_CREATED = '003'
87
- RPL_MYINFO = '004'
88
- RPL_BOUNCE = '005'
89
- RPL_USERHOST = '302'
90
- RPL_ISON = '303'
91
- RPL_AWAY = '301'
92
- RPL_UNAWAY = '305'
93
- RPL_NOWAWAY = '306'
94
- RPL_WHOISUSER = '311'
95
- RPL_WHOISSERVER = '312'
96
- RPL_WHOISOPERATOR = '313'
97
- RPL_WHOISIDLE = '317'
98
- RPL_ENDOFWHOIS = '318'
99
- RPL_WHOISCHANNELS = '319'
100
- RPL_WHOWASUSER = '314'
101
- RPL_ENDOFWHOWAS = '369'
102
- RPL_LISTSTART = '321'
103
- RPL_LIST = '322'
104
- RPL_LISTEND = '323'
105
- RPL_UNIQOPIS = '325'
106
- RPL_CHANNELMODEIS = '324'
107
- RPL_NOTOPIC = '331'
108
- RPL_TOPIC = '332'
109
- RPL_INVITING = '341'
110
- RPL_SUMMONING = '342'
111
- RPL_INVITELIST = '346'
112
- RPL_ENDOFINVITELIST = '347'
113
- RPL_EXCEPTLIST = '348'
114
- RPL_ENDOFEXCEPTLIST = '349'
115
- RPL_VERSION = '351'
116
- RPL_WHOREPLY = '352'
117
- RPL_ENDOFWHO = '315'
118
- RPL_NAMREPLY = '353'
119
- RPL_ENDOFNAMES = '366'
120
- RPL_LINKS = '364'
121
- RPL_ENDOFLINKS = '365'
122
- RPL_BANLIST = '367'
123
- RPL_ENDOFBANLIST = '368'
124
- RPL_INFO = '371'
125
- RPL_ENDOFINFO = '374'
126
- RPL_MOTDSTART = '375'
127
- RPL_MOTD = '372'
128
- RPL_ENDOFMOTD = '376'
129
- RPL_YOUREOPER = '381'
130
- RPL_REHASHING = '382'
131
- RPL_YOURESERVICE = '383'
132
- RPL_TIME = '391'
133
- RPL_USERSSTART = '392'
134
- RPL_USERS = '393'
135
- RPL_ENDOFUSERS = '394'
136
- RPL_NOUSERS = '395'
137
- RPL_TRACELINK = '200'
138
- RPL_TRACECONNECTING = '201'
139
- RPL_TRACEHANDSHAKE = '202'
140
- RPL_TRACEUNKNOWN = '203'
141
- RPL_TRACEOPERATOR = '204'
142
- RPL_TRACEUSER = '205'
143
- RPL_TRACESERVER = '206'
144
- RPL_TRACESERVICE = '207'
145
- RPL_TRACENEWTYPE = '208'
146
- RPL_TRACECLASS = '209'
147
- RPL_TRACERECONNECT = '210'
148
- RPL_TRACELOG = '261'
149
- RPL_TRACEEND = '262'
150
- RPL_STATSLINKINFO = '211'
151
- RPL_STATSCOMMANDS = '212'
152
- RPL_ENDOFSTATS = '219'
153
- RPL_STATSUPTIME = '242'
154
- RPL_STATSOLINE = '243'
155
- RPL_UMODEIS = '221'
156
- RPL_SERVLIST = '234'
157
- RPL_SERVLISTEND = '235'
158
- RPL_LUSERCLIENT = '251'
159
- RPL_LUSEROP = '252'
160
- RPL_LUSERUNKNOWN = '253'
161
- RPL_LUSERCHANNELS = '254'
162
- RPL_LUSERME = '255'
163
- RPL_ADMINME = '256'
164
- RPL_ADMINLOC1 = '257'
165
- RPL_ADMINLOC2 = '258'
166
- RPL_ADMINEMAIL = '259'
167
- RPL_TRYAGAIN = '263'
168
- ERR_NOSUCHNICK = '401'
169
- ERR_NOSUCHSERVER = '402'
170
- ERR_NOSUCHCHANNEL = '403'
171
- ERR_CANNOTSENDTOCHAN = '404'
172
- ERR_TOOMANYCHANNELS = '405'
173
- ERR_WASNOSUCHNICK = '406'
174
- ERR_TOOMANYTARGETS = '407'
175
- ERR_NOSUCHSERVICE = '408'
176
- ERR_NOORIGIN = '409'
177
- ERR_NORECIPIENT = '411'
178
- ERR_NOTEXTTOSEND = '412'
179
- ERR_NOTOPLEVEL = '413'
180
- ERR_WILDTOPLEVEL = '414'
181
- ERR_BADMASK = '415'
182
- ERR_UNKNOWNCOMMAND = '421'
183
- ERR_NOMOTD = '422'
184
- ERR_NOADMININFO = '423'
185
- ERR_FILEERROR = '424'
186
- ERR_NONICKNAMEGIVEN = '431'
187
- ERR_ERRONEUSNICKNAME = '432'
188
- ERR_NICKNAMEINUSE = '433'
189
- ERR_NICKCOLLISION = '436'
190
- ERR_UNAVAILRESOURCE = '437'
191
- ERR_USERNOTINCHANNEL = '441'
192
- ERR_NOTONCHANNEL = '442'
193
- ERR_USERONCHANNEL = '443'
194
- ERR_NOLOGIN = '444'
195
- ERR_SUMMONDISABLED = '445'
196
- ERR_USERSDISABLED = '446'
197
- ERR_NOTREGISTERED = '451'
198
- ERR_NEEDMOREPARAMS = '461'
199
- ERR_ALREADYREGISTRED = '462'
200
- ERR_NOPERMFORHOST = '463'
201
- ERR_PASSWDMISMATCH = '464'
202
- ERR_YOUREBANNEDCREEP = '465'
203
- ERR_YOUWILLBEBANNED = '466'
204
- ERR_KEYSET = '467'
205
- ERR_CHANNELISFULL = '471'
206
- ERR_UNKNOWNMODE = '472'
207
- ERR_INVITEONLYCHAN = '473'
208
- ERR_BANNEDFROMCHAN = '474'
209
- ERR_BADCHANNELKEY = '475'
210
- ERR_BADCHANMASK = '476'
211
- ERR_NOCHANMODES = '477'
212
- ERR_BANLISTFULL = '478'
213
- ERR_NOPRIVILEGES = '481'
214
- ERR_CHANOPRIVSNEEDED = '482'
215
- ERR_CANTKILLSERVER = '483'
216
- ERR_RESTRICTED = '484'
217
- ERR_UNIQOPPRIVSNEEDED = '485'
218
- ERR_NOOPERHOST = '491'
219
- ERR_UMODEUNKNOWNFLAG = '501'
220
- ERR_USERSDONTMATCH = '502'
221
- RPL_SERVICEINFO = '231'
222
- RPL_ENDOFSERVICES = '232'
223
- RPL_SERVICE = '233'
224
- RPL_NONE = '300'
225
- RPL_WHOISCHANOP = '316'
226
- RPL_KILLDONE = '361'
227
- RPL_CLOSING = '362'
228
- RPL_CLOSEEND = '363'
229
- RPL_INFOSTART = '373'
230
- RPL_MYPORTIS = '384'
231
- RPL_STATSCLINE = '213'
232
- RPL_STATSNLINE = '214'
233
- RPL_STATSILINE = '215'
234
- RPL_STATSKLINE = '216'
235
- RPL_STATSQLINE = '217'
236
- RPL_STATSYLINE = '218'
237
- RPL_STATSVLINE = '240'
238
- RPL_STATSLLINE = '241'
239
- RPL_STATSHLINE = '244'
240
- RPL_STATSSLINE = '244'
241
- RPL_STATSPING = '246'
242
- RPL_STATSBLINE = '247'
243
- RPL_STATSDLINE = '250'
244
- ERR_NOSERVICEHOST = '492'
245
-
246
- PASS = 'PASS'
247
- NICK = 'NICK'
248
- USER = 'USER'
249
- OPER = 'OPER'
250
- MODE = 'MODE'
251
- SERVICE = 'SERVICE'
252
- QUIT = 'QUIT'
253
- SQUIT = 'SQUIT'
254
- JOIN = 'JOIN'
255
- PART = 'PART'
256
- TOPIC = 'TOPIC'
257
- NAMES = 'NAMES'
258
- LIST = 'LIST'
259
- INVITE = 'INVITE'
260
- KICK = 'KICK'
261
- PRIVMSG = 'PRIVMSG'
262
- NOTICE = 'NOTICE'
263
- MOTD = 'MOTD'
264
- LUSERS = 'LUSERS'
265
- VERSION = 'VERSION'
266
- STATS = 'STATS'
267
- LINKS = 'LINKS'
268
- TIME = 'TIME'
269
- CONNECT = 'CONNECT'
270
- TRACE = 'TRACE'
271
- ADMIN = 'ADMIN'
272
- INFO = 'INFO'
273
- SERVLIST = 'SERVLIST'
274
- SQUERY = 'SQUERY'
275
- WHO = 'WHO'
276
- WHOIS = 'WHOIS'
277
- WHOWAS = 'WHOWAS'
278
- KILL = 'KILL'
279
- PING = 'PING'
280
- PONG = 'PONG'
281
- ERROR = 'ERROR'
282
- AWAY = 'AWAY'
283
- REHASH = 'REHASH'
284
- DIE = 'DIE'
285
- RESTART = 'RESTART'
286
- SUMMON = 'SUMMON'
287
- USERS = 'USERS'
288
- WALLOPS = 'WALLOPS'
289
- USERHOST = 'USERHOST'
290
- ISON = 'ISON'
291
- end
292
-
293
- COMMANDS = Constants.constants.inject({}) {|r,i| # :nodoc:
294
- r.update(Constants.const_get(i) => i)
295
- }
18
+ autoload :Message, "net/irc/message"
19
+ autoload :Client, "net/irc/client"
20
+ autoload :Server, "net/irc/server"
296
21
 
297
22
  class Prefix < String
298
23
  def nick
@@ -332,533 +57,3 @@ module Net::IRC
332
57
  module_function :ctcp_decoding
333
58
  end
334
59
 
335
- class Net::IRC::Message
336
- include Net::IRC
337
-
338
- class InvalidMessage < Net::IRC::IRCException; end
339
-
340
- attr_reader :prefix, :command, :params
341
-
342
- # Parse string and return new Message.
343
- # If the string is invalid message, this method raises Net::IRC::Message::InvalidMessage.
344
- def self.parse(str)
345
- _, prefix, command, *rest = *PATTERN::MESSAGE_PATTERN.match(str)
346
- raise InvalidMessage, "Invalid message: #{str.dump}" unless _
347
-
348
- case
349
- when rest[0] && !rest[0].empty?
350
- middle, trailer, = *rest
351
- when rest[2] && !rest[2].empty?
352
- middle, trailer, = *rest[2, 2]
353
- when rest[1]
354
- params = []
355
- trailer = rest[1]
356
- when rest[3]
357
- params = []
358
- trailer = rest[3]
359
- else
360
- params = []
361
- end
362
-
363
- params ||= middle.split(/ /)[1..-1]
364
- params << trailer if trailer
365
-
366
- new(prefix, command, params)
367
- end
368
-
369
- def initialize(prefix, command, params)
370
- @prefix = Prefix.new(prefix.to_s)
371
- @command = command
372
- @params = params
373
- end
374
-
375
- # Same as @params[n].
376
- def [](n)
377
- @params[n]
378
- end
379
-
380
- # Iterate params.
381
- def each(&block)
382
- @params.each(&block)
383
- end
384
-
385
- # Stringfy message to raw IRC message.
386
- def to_s
387
- str = ""
388
-
389
- str << ":#{@prefix} " unless @prefix.empty?
390
- str << @command
391
-
392
- if @params
393
- f = false
394
- @params.each do |param|
395
- str << " "
396
- if !f && (param.size == 0 || / / =~ param || /^:/ =~ param)
397
- str << ":#{param}"
398
- f = true
399
- else
400
- str << param
401
- end
402
- end
403
- end
404
-
405
- str << "\x0D\x0A"
406
-
407
- str
408
- end
409
- alias to_str to_s
410
-
411
- # Same as params.
412
- def to_a
413
- @params
414
- end
415
-
416
- # If the message is CTCP, return true.
417
- def ctcp?
418
- message = @params[1]
419
- message[0] == 1 && message[message.length-1] == 1
420
- end
421
-
422
- def inspect
423
- '#<%s:0x%x prefix:%s command:%s params:%s>' % [
424
- self.class,
425
- self.object_id,
426
- @prefix,
427
- @command,
428
- @params.inspect
429
- ]
430
- end
431
-
432
- end # Message
433
-
434
- class Net::IRC::Client
435
- include Net::IRC
436
- include Constants
437
-
438
- attr_reader :host, :port, :opts
439
- attr_reader :prefix, :channels
440
-
441
- def initialize(host, port, opts={})
442
- @host = host
443
- @port = port
444
- @opts = OpenStruct.new(opts)
445
- @log = @opts.logger || Logger.new($stdout)
446
- @channels = {
447
- # "#channel" => {
448
- # :modes => [],
449
- # :users => [],
450
- # }
451
- }
452
- @channels.extend(MonitorMixin)
453
- end
454
-
455
- # Connect to server and start loop.
456
- def start
457
- @socket = TCPSocket.open(@host, @port)
458
- on_connected
459
- post PASS, @opts.pass if @opts.pass
460
- post NICK, @opts.nick
461
- post USER, @opts.user, "0", "*", @opts.real
462
- while l = @socket.gets
463
- begin
464
- @log.debug "RECEIVE: #{l.chomp}"
465
- m = Message.parse(l)
466
- next if on_message(m) === true
467
- name = "on_#{(COMMANDS[m.command.upcase] || m.command).downcase}"
468
- send(name, m) if respond_to?(name)
469
- rescue Exception => e
470
- warn e
471
- warn e.backtrace.join("\r\t")
472
- raise
473
- rescue Message::InvalidMessage
474
- @log.error "MessageParse: " + l.inspect
475
- end
476
- end
477
- rescue IOError
478
- ensure
479
- finish
480
- end
481
-
482
- # Close connection to server.
483
- def finish
484
- begin
485
- @socket.close
486
- rescue
487
- end
488
- on_disconnected
489
- end
490
-
491
- # Catch all messages.
492
- # If this method return true, aother callback will not be called.
493
- def on_message(m)
494
- end
495
-
496
- # Default RPL_WELCOME callback.
497
- # This sets @prefix from the message.
498
- def on_rpl_welcome(m)
499
- @prefix = Prefix.new(m[1][/\S+$/])
500
- end
501
-
502
- # Default PING callback. Response PONG.
503
- def on_ping(m)
504
- post PONG, @prefix ? @prefix.nick : ""
505
- end
506
-
507
- # For managing channel
508
- def on_rpl_namreply(m)
509
- type = m[1]
510
- channel = m[2]
511
- init_channel(channel)
512
-
513
- @channels.synchronize do
514
- m[3].split(/\s+/).each do |u|
515
- _, mode, nick = *u.match(/^([@+]?)(.+)/)
516
-
517
- @channels[channel][:users] << nick
518
- @channels[channel][:users].uniq!
519
-
520
- case mode
521
- when "@" # channel operator
522
- @channels[channel][:modes] << ["o", nick]
523
- when "+" # voiced (under moderating mode)
524
- @channels[channel][:modes] << ["v", nick]
525
- end
526
- end
527
-
528
- case type
529
- when "@" # secret
530
- @channels[channel][:modes] << ["s", nil]
531
- when "*" # private
532
- @channels[channel][:modes] << ["p", nil]
533
- when "=" # public
534
- end
535
-
536
- @channels[channel][:modes].uniq!
537
- end
538
- end
539
-
540
- # For managing channel
541
- def on_part(m)
542
- nick = m.prefix.nick
543
- channel = m[0]
544
- init_channel(channel)
545
-
546
- @channels.synchronize do
547
- info = @channels[channel]
548
- if info
549
- info[:users].delete(nick)
550
- info[:modes].delete_if {|u|
551
- u[1] == nick
552
- }
553
- end
554
- end
555
- end
556
-
557
- # For managing channel
558
- def on_quit(m)
559
- nick = m.prefix.nick
560
-
561
- @channels.synchronize do
562
- @channels.each do |channel, info|
563
- info[:users].delete(nick)
564
- info[:modes].delete_if {|u|
565
- u[1] == nick
566
- }
567
- end
568
- end
569
- end
570
-
571
- # For managing channel
572
- def on_kick(m)
573
- users = m[1].split(/,/)
574
-
575
- @channels.synchronize do
576
- m[0].split(/,/).each do |chan|
577
- init_channel(chan)
578
- info = @channels[chan]
579
- if info
580
- users.each do |nick|
581
- info[:users].delete(nick)
582
- info[:modes].delete_if {|u|
583
- u[1] == nick
584
- }
585
- end
586
- end
587
- end
588
- end
589
- end
590
-
591
- # For managing channel
592
- def on_join(m)
593
- nick = m.prefix.nick
594
- channel = m[0]
595
-
596
- @channels.synchronize do
597
- init_channel(channel)
598
-
599
- @channels[channel][:users] << nick
600
- @channels[channel][:users].uniq!
601
- end
602
- end
603
-
604
- # For managing channel
605
- def on_mode(m)
606
- channel = m[0]
607
- @channels.synchronize do
608
- init_channel(channel)
609
-
610
- positive_mode = []
611
- negative_mode = []
612
-
613
- mode = positive_mode
614
- arg_pos = 0
615
- m[1].each_byte do |c|
616
- case c
617
- when ?+
618
- mode = positive_mode
619
- when ?-
620
- mode = negative_mode
621
- when ?o, ?v, ?k, ?l, ?b, ?e, ?I
622
- mode << [c.chr, m[arg_pos + 2]]
623
- arg_pos += 1
624
- else
625
- mode << [c.chr, nil]
626
- end
627
- end
628
- mode = nil
629
-
630
- negative_mode.each do |m|
631
- @channels[channel][:modes].delete(m)
632
- end
633
-
634
- positive_mode.each do |m|
635
- @channels[channel][:modes] << m
636
- end
637
-
638
- @channels[channel][:modes].uniq!
639
- [negative_mode, positive_mode]
640
- end
641
- end
642
-
643
- # For managing channel
644
- def init_channel(channel)
645
- @channels[channel] ||= {
646
- :modes => [],
647
- :users => [],
648
- }
649
- end
650
-
651
- # Do nothing.
652
- # This is for avoiding error on calling super.
653
- # So you can always call super at subclass.
654
- def method_missing(name, *args)
655
- end
656
-
657
- # Call when socket connected.
658
- def on_connected
659
- end
660
-
661
- # Call when socket closed.
662
- def on_disconnected
663
- end
664
-
665
- private
666
-
667
- # Post message to server.
668
- #
669
- # include Net::IRC::Constans
670
- # post PRIVMSG, "#channel", "foobar"
671
- def post(command, *params)
672
- m = Message.new(nil, command, params.map {|s|
673
- s ? s.gsub(/[\r\n]/, " ") : ""
674
- })
675
-
676
- @log.debug "SEND: #{m.to_s.chomp}"
677
- @socket << m
678
- end
679
- end # Client
680
-
681
- class Net::IRC::Server
682
- def initialize(host, port, session_class, opts={})
683
- @host = host
684
- @port = port
685
- @session_class = session_class
686
- @opts = OpenStruct.new(opts)
687
- @sessions = []
688
- end
689
-
690
- # Start server loop.
691
- def start
692
- @serv = TCPServer.new(@host, @port)
693
- @log = @opts.logger || Logger.new($stdout)
694
- @log.info "Host: #{@host} Port:#{@port}"
695
- @accept = Thread.start do
696
- loop do
697
- Thread.start(@serv.accept) do |s|
698
- begin
699
- @log.info "Client connected, new session starting..."
700
- s = @session_class.new(self, s, @log, @opts)
701
- @sessions << s
702
- s.start
703
- rescue Exception => e
704
- puts e
705
- puts e.backtrace
706
- ensure
707
- @sessions.delete(s)
708
- end
709
- end
710
- end
711
- end
712
- @accept.join
713
- end
714
-
715
- # Close all sessions.
716
- def finish
717
- Thread.exclusive do
718
- @accept.kill
719
- begin
720
- @serv.close
721
- rescue
722
- end
723
- @sessions.each do |s|
724
- s.finish
725
- end
726
- end
727
- end
728
-
729
-
730
- class Session
731
- include Net::IRC
732
- include Constants
733
-
734
- attr_reader :prefix, :nick, :real, :host
735
-
736
- # Override subclass.
737
- def server_name
738
- "net-irc"
739
- end
740
-
741
- # Override subclass.
742
- def server_version
743
- "0.0.0"
744
- end
745
-
746
- # Override subclass.
747
- def avaiable_user_modes
748
- "eixwy"
749
- end
750
-
751
- # Override subclass.
752
- def avaiable_channel_modes
753
- "spknm"
754
- end
755
-
756
- def initialize(server, socket, logger, opts={})
757
- @server, @socket, @log, @opts = server, socket, logger, opts
758
- end
759
-
760
- def self.start(*args)
761
- new(*args).start
762
- end
763
-
764
- # Start session loop.
765
- def start
766
- on_connected
767
- while l = @socket.gets
768
- begin
769
- @log.debug "RECEIVE: #{l.chomp}"
770
- m = Message.parse(l)
771
- next if on_message(m) === true
772
-
773
- name = "on_#{(COMMANDS[m.command.upcase] || m.command).downcase}"
774
- send(name, m) if respond_to?(name)
775
-
776
- break if m.command == QUIT
777
- rescue Message::InvalidMessage
778
- @log.error "MessageParse: " + l.inspect
779
- end
780
- end
781
- rescue IOError
782
- ensure
783
- finish
784
- end
785
-
786
- # Close this session.
787
- def finish
788
- begin
789
- @socket.close
790
- rescue
791
- end
792
- on_disconnected
793
- end
794
-
795
- # Default PASS callback.
796
- # Set @pass.
797
- def on_pass(m)
798
- @pass = m.params[0]
799
- end
800
-
801
- # Default NICK callback.
802
- # Set @nick.
803
- def on_nick(m)
804
- @nick = m.params[0]
805
- end
806
-
807
- # Default USER callback.
808
- # Set @user, @real, @host and call initial_message.
809
- def on_user(m)
810
- @user, @real = m.params[0], m.params[3]
811
- @host = @socket.peeraddr[2]
812
- @prefix = Prefix.new("#{@nick}!#{@user}@#{@host}")
813
- initial_message
814
- end
815
-
816
- # Call when socket connected.
817
- def on_connected
818
- end
819
-
820
- # Call when socket closed.
821
- def on_disconnected
822
- end
823
-
824
- # Catch all messages.
825
- # If this method return true, aother callback will not be called.
826
- def on_message(m)
827
- end
828
-
829
- # Default PING callback. Response PONG.
830
- def on_ping(m)
831
- post server_name, PONG, m.params[0]
832
- end
833
-
834
- # Do nothing.
835
- # This is for avoiding error on calling super.
836
- # So you can always call super at subclass.
837
- def method_missing(name, *args)
838
- end
839
-
840
- private
841
- # Post message to server.
842
- #
843
- # include Net::IRC::Constans
844
- # post prefix, PRIVMSG, "#channel", "foobar"
845
- def post(prefix, command, *params)
846
- m = Message.new(prefix, command, params.map {|s|
847
- s.gsub(/[\r\n]/, " ")
848
- })
849
- @log.debug "SEND: #{m.to_s.chomp}"
850
- @socket << m
851
- rescue IOError
852
- finish
853
- end
854
-
855
- # Call when client connected.
856
- # Send RPL_WELCOME sequence. If you want to customize, override this method at subclass.
857
- def initial_message
858
- post server_name, RPL_WELCOME, @nick, "Welcome to the Internet Relay Network #{@prefix}"
859
- post server_name, RPL_YOURHOST, @nick, "Your host is #{server_name}, running version #{server_version}"
860
- post server_name, RPL_CREATED, @nick, "This server was created #{Time.now}"
861
- post server_name, RPL_MYINFO, @nick, "#{server_name} #{server_version} #{avaiable_user_modes} #{avaiable_channel_modes}"
862
- end
863
- end
864
- end # Server