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/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