net-irc 0.0.4 → 0.0.5

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.
@@ -3,11 +3,11 @@
3
3
 
4
4
  # wig.rb
5
5
 
6
- Wassr IRC Gateway
6
+ wig.rb channel: http://wassr.jp/channel/wigrb
7
7
 
8
8
  ## Launch
9
9
 
10
- $ ruby wig.rb # daemonized
10
+ $ ruby wig.rb
11
11
 
12
12
  If you want to help:
13
13
 
@@ -21,8 +21,8 @@ Configuration example for Tiarra ( http://coderepos.org/share/wiki/Tiarra ).
21
21
 
22
22
  wassr {
23
23
  host: localhost
24
- port: 16670
25
- name: username@example.com athack jabber=username@example.com:jabberpasswd
24
+ port: 16668
25
+ name: username@example.com athack jabber=username@example.com:jabberpasswd tid=10 ratio=10:3:5
26
26
  password: password on Wassr
27
27
  in-encoding: utf8
28
28
  out-encoding: utf8
@@ -33,12 +33,37 @@ Configuration example for Tiarra ( http://coderepos.org/share/wiki/Tiarra ).
33
33
  If `athack` client option specified,
34
34
  all nick in join message is leading with @.
35
35
 
36
- So if you complemente nicks (ex. Irssi),
36
+ So if you complemente nicks (e.g. Irssi),
37
37
  it's good for Twitter like reply command (@nick).
38
38
 
39
39
  In this case, you will see torrent of join messages after connected,
40
40
  because NAMES list can't send @ leading nick (it interpreted op.)
41
41
 
42
+ ### tid=<color>
43
+
44
+ Apply id to each message for make favorites by CTCP ACTION.
45
+
46
+ /me fav id
47
+
48
+ <color> can be
49
+
50
+ 0 => white
51
+ 1 => black
52
+ 2 => blue navy
53
+ 3 => green
54
+ 4 => red
55
+ 5 => brown maroon
56
+ 6 => purple
57
+ 7 => orange olive
58
+ 8 => yellow
59
+ 9 => lightgreen lime
60
+ 10 => teal
61
+ 11 => lightcyan cyan aqua
62
+ 12 => lightblue royal
63
+ 13 => pink lightpurple fuchsia
64
+ 14 => grey
65
+ 15 => lightgrey silver
66
+
42
67
 
43
68
  ### jabber=<jid>:<pass>
44
69
 
@@ -46,23 +71,45 @@ If `jabber=<jid>:<pass>` option specified,
46
71
  use jabber to get friends timeline.
47
72
 
48
73
  You must setup im notifing settings in the site and
49
- install 'xmpp4r-simple' gem.
74
+ install "xmpp4r-simple" gem.
50
75
 
51
76
  $ sudo gem install xmpp4r-simple
52
77
 
53
78
  Be careful for managing password.
54
79
 
55
- ## Licence
80
+ ### alwaysim
81
+
82
+ Use IM instead of any APIs (e.g. post)
83
+
84
+ ### ratio=<timeline>:<friends>:<channel>
85
+
86
+ ## License
56
87
 
57
88
  Ruby's by cho45
58
89
 
59
90
  =end
60
91
 
61
- $LOAD_PATH << File.dirname(__FILE__)
92
+ $LOAD_PATH << "lib"
93
+ $LOAD_PATH << "../lib"
62
94
 
63
- require "tig.rb"
95
+ $KCODE = "u" # json use this
64
96
 
65
- class WassrIrcGateway < TwitterIrcGateway
97
+ require "rubygems"
98
+ require "net/irc"
99
+ require "net/http"
100
+ require "uri"
101
+ require "json"
102
+ require "socket"
103
+ require "time"
104
+ require "logger"
105
+ require "yaml"
106
+ require "pathname"
107
+ require "cgi"
108
+ require "digest/md5"
109
+
110
+ Net::HTTP.version_1_2
111
+
112
+ class WassrIrcGateway < Net::IRC::Server::Session
66
113
  def server_name
67
114
  "wassrgw"
68
115
  end
@@ -86,6 +133,562 @@ class WassrIrcGateway < TwitterIrcGateway
86
133
  def jabber_bot_id
87
134
  "wassr-bot@wassr.jp"
88
135
  end
136
+
137
+ def hourly_limit
138
+ 60
139
+ end
140
+
141
+ class ApiFailed < StandardError; end
142
+
143
+ def initialize(*args)
144
+ super
145
+ @channels = {}
146
+ @user_agent = "#{self.class}/#{server_version} (wig.rb)"
147
+ @map = nil
148
+ @counters = {} # for jabber fav
149
+ end
150
+
151
+ def on_user(m)
152
+ super
153
+ post @prefix, JOIN, main_channel
154
+ post server_name, MODE, main_channel, "+o", @prefix.nick
155
+
156
+ @real, *@opts = @opts.name || @real.split(/\s+/)
157
+ @opts = @opts.inject({}) {|r,i|
158
+ key, value = i.split("=")
159
+ r.update(key => value)
160
+ }
161
+ @tmap = TypableMap.new
162
+
163
+ if @opts["jabber"]
164
+ jid, pass = @opts["jabber"].split(":", 2)
165
+ @opts["jabber"].replace("jabber=#{jid}:********")
166
+ if jabber_bot_id
167
+ begin
168
+ require "xmpp4r-simple"
169
+ start_jabber(jid, pass)
170
+ rescue LoadError
171
+ log "Failed to start Jabber."
172
+ log 'Installl "xmpp4r-simple" gem or check your id/pass.'
173
+ finish
174
+ end
175
+ else
176
+ @opts.delete("jabber")
177
+ log "This gateway does not support Jabber bot."
178
+ end
179
+ end
180
+
181
+ log "Client Options: #{@opts.inspect}"
182
+ @log.info "Client Options: #{@opts.inspect}"
183
+
184
+ timeline_ratio, friends_ratio, channel_ratio = (@opts["ratio"] || "10:3:5").split(":").map {|ratio| ratio.to_i }
185
+ footing = (timeline_ratio + friends_ratio + channel_ratio).to_f
186
+
187
+ @timeline = []
188
+ @check_friends_thread = Thread.start do
189
+ loop do
190
+ begin
191
+ check_friends
192
+ rescue ApiFailed => e
193
+ @log.error e.inspect
194
+ rescue Exception => e
195
+ @log.error e.inspect
196
+ e.backtrace.each do |l|
197
+ @log.error "\t#{l}"
198
+ end
199
+ end
200
+ sleep freq(friends_ratio / footing)
201
+ end
202
+ end
203
+
204
+ return if @opts["jabber"]
205
+
206
+ @check_timeline_thread = Thread.start do
207
+ sleep 3
208
+ loop do
209
+ begin
210
+ check_timeline
211
+ # check_direct_messages
212
+ rescue ApiFailed => e
213
+ @log.error e.inspect
214
+ rescue Exception => e
215
+ @log.error e.inspect
216
+ e.backtrace.each do |l|
217
+ @log.error "\t#{l}"
218
+ end
219
+ end
220
+ sleep freq(timeline_ratio / footing)
221
+ end
222
+ end
223
+
224
+ @check_channel_thread = Thread.start do
225
+ sleep 5
226
+ Thread.abort_on_exception= true
227
+ loop do
228
+ begin
229
+ check_channel
230
+ # check_direct_messages
231
+ rescue ApiFailed => e
232
+ @log.error e.inspect
233
+ rescue Exception => e
234
+ @log.error e.inspect
235
+ e.backtrace.each do |l|
236
+ @log.error "\t#{l}"
237
+ end
238
+ end
239
+ sleep freq(channel_ratio / footing)
240
+ end
241
+ end
242
+ end
243
+
244
+ def on_disconnected
245
+ @check_friends_thread.kill rescue nil
246
+ @check_timeline_thread.kill rescue nil
247
+ @check_channel_thread.kill rescue nil
248
+ @im_thread.kill rescue nil
249
+ @im.disconnect rescue nil
250
+ end
251
+
252
+ def on_privmsg(m)
253
+ return on_ctcp(m[0], ctcp_decoding(m[1])) if m.ctcp?
254
+ retry_count = 3
255
+ ret = nil
256
+ target, message = *m.params
257
+ begin
258
+ if target =~ /^#(.+)/
259
+ channel = Regexp.last_match[1]
260
+ if @opts.key?("alwaysim") && @im && @im.connected? # in jabber mode, using jabber post
261
+ message = "##{channel} #{message}" unless "##{channel}" == main_channel
262
+ ret = @im.deliver(jabber_bot_id, message)
263
+ post "#{nick}!#{nick}@#{api_base.host}", TOPIC, channel, untinyurl(message)
264
+ else
265
+ if "##{channel}" == main_channel
266
+ ret = api("statuses/update", {"status" => message})
267
+ else
268
+ ret = api("channel_message/update", {"name_en" => channel, "body" => message})
269
+ end
270
+ end
271
+ else
272
+ # direct message
273
+ ret = api("direct_messages/new", {"user" => target, "text" => message})
274
+ end
275
+ raise ApiFailed, "API failed" unless ret
276
+ log "Status Updated" unless @im && @im.connected?
277
+ rescue => e
278
+ @log.error [retry_count, e.inspect].inspect
279
+ if retry_count > 0
280
+ retry_count -= 1
281
+ @log.debug "Retry to setting status..."
282
+ retry
283
+ else
284
+ log "Some Error Happened on Sending #{message}. #{e}"
285
+ end
286
+ end
287
+ end
288
+
289
+ def on_ctcp(target, message)
290
+ _, command, *args = message.split(/\s+/)
291
+ case command
292
+ when "list"
293
+ nick = args[0]
294
+ @log.debug([ nick, message ])
295
+ res = api("statuses/user_timeline", { "id" => nick }).reverse_each do |s|
296
+ @log.debug(s)
297
+ post nick, NOTICE, main_channel, "#{generate_status_message(s)}"
298
+ end
299
+
300
+ unless res
301
+ post nil, ERR_NOSUCHNICK, nick, "No such nick/channel"
302
+ end
303
+ when "fav"
304
+ tid = args[0]
305
+ st = @tmap[tid]
306
+ if st
307
+ if @im && @im.connected?
308
+ # IM のときはいろいろめんどうなことする
309
+ nick, count = *st
310
+ pos = @counters[nick] - count
311
+ @log.debug "%p %s %d/%d => %d" % [
312
+ st,
313
+ nick,
314
+ count,
315
+ @counters[nick],
316
+ pos
317
+ ]
318
+ res = api("statuses/user_timeline", { "id" => nick })[pos]
319
+ id = res["rid"]
320
+ else
321
+ id = st["id"] || st["rid"]
322
+ end
323
+ res = api("favorites/create/#{id}", {})
324
+ post nil, NOTICE, main_channel, "Fav: #{res["screen_name"]}: #{res["text"]}"
325
+ else
326
+ post nil, NOTICE, main_channel, "No such id #{tid}"
327
+ end
328
+ when "link"
329
+ tid = args[0]
330
+ st = @tmap[tid]
331
+ if st
332
+ st["link"] = (api_base + "/#{st["user"]["screen_name"]}/statuses/#{st["id"]}").to_s unless st["link"]
333
+ post nil, NOTICE, main_channel, st["link"]
334
+ else
335
+ post nil, NOTICE, main_channel, "No such id #{tid}"
336
+ end
337
+ end
338
+ rescue ApiFailed => e
339
+ log e.inspect
340
+ end
341
+
342
+ def on_whois(m)
343
+ nick = m.params[0]
344
+ f = (@friends || []).find {|i| i["screen_name"] == nick }
345
+ if f
346
+ post nil, RPL_WHOISUSER, @nick, nick, nick, api_base.host, "*", "#{f["name"]} / #{f["description"]}"
347
+ post nil, RPL_WHOISSERVER, @nick, nick, api_base.host, api_base.to_s
348
+ post nil, RPL_WHOISIDLE, @nick, nick, "0", "seconds idle"
349
+ post nil, RPL_ENDOFWHOIS, @nick, nick, "End of WHOIS list"
350
+ else
351
+ post nil, ERR_NOSUCHNICK, nick, "No such nick/channel"
352
+ end
353
+ end
354
+
355
+ def on_join(m)
356
+ channels = m.params[0].split(/\s*,\s*/)
357
+ channels.each do |channel|
358
+ next if channel == main_channel
359
+ @channels[channel] = {
360
+ :read => []
361
+ }
362
+ post "#{@nick}!#{@nick}@#{api_base.host}", JOIN, channel
363
+ end
364
+ end
365
+
366
+ def on_part(m)
367
+ channel = m.params[0]
368
+ return if channel == main_channel
369
+ @channels.delete(channel)
370
+ post "#{@nick}!#{@nick}@#{api_base.host}", PART, channel
371
+ end
372
+
373
+ def on_who(m)
374
+ channel = m.params[0]
375
+ case
376
+ when channel == main_channel
377
+ # "<channel> <user> <host> <server> <nick>
378
+ # ( "H" / "G" > ["*"] [ ( "@" / "+" ) ]
379
+ # :<hopcount> <real name>"
380
+ @friends.each do |f|
381
+ user = nick = f["screen_name"]
382
+ host = serv = api_base.host
383
+ real = f["name"]
384
+ post nil, RPL_WHOREPLY, @nick, channel, user, host, serv, nick, "H*@", "0 #{real}"
385
+ end
386
+ post nil, RPL_ENDOFWHO, @nick, channel
387
+ when @groups.key?(channel)
388
+ @groups[channel].each do |name|
389
+ f = @friends.find {|i| i["screen_name"] == name }
390
+ user = nick = f["screen_name"]
391
+ host = serv = api_base.host
392
+ real = f["name"]
393
+ post nil, RPL_WHOREPLY, @nick, channel, user, host, serv, nick, "H*@", "0 #{real}"
394
+ end
395
+ post nil, RPL_ENDOFWHO, @nick, channel
396
+ else
397
+ post nil, ERR_NOSUCHNICK, @nick, nick, "No such nick/channel"
398
+ end
399
+ end
400
+
401
+ private
402
+ def check_timeline
403
+ @prev_time ||= Time.at(0)
404
+ api("statuses/friends_timeline", {"since" => @prev_time.httpdate}).reverse_each do |s|
405
+ id = s["id"] || s["rid"]
406
+ next if id.nil? || @timeline.include?(id)
407
+ @timeline << id
408
+ nick = s["user_login_id"]
409
+ mesg = generate_status_message(s)
410
+
411
+ tid = @tmap.push(s)
412
+
413
+ @log.debug [id, nick, mesg]
414
+ if nick == @nick # 自分のときは topic に
415
+ post "#{nick}!#{nick}@#{api_base.host}", TOPIC, main_channel, untinyurl(mesg)
416
+ else
417
+ if @opts["tid"]
418
+ message(nick, main_channel, "%s \x03%s [%s]" % [mesg, @opts["tid"], tid])
419
+ else
420
+ message(nick, main_channel, "%s" % [mesg, tid])
421
+ end
422
+ end
423
+ end
424
+ @log.debug "@timeline.size = #{@timeline.size}"
425
+ @timeline = @timeline.last(100)
426
+ @prev_time = Time.now
427
+ end
428
+
429
+ def check_channel
430
+ @channels.keys.each do |channel|
431
+ @log.debug "getting channel -> #{channel}..."
432
+ api("channel_message/list", { "name_en" => channel.sub(/^#/, "") }).reverse_each do |s|
433
+ begin
434
+ id = Digest::MD5.hexdigest(s["user"]["login_id"] + s["body"])
435
+ next if @channels[channel][:read].include?(id)
436
+ @channels[channel][:read] << id
437
+ nick = s["user"]["login_id"]
438
+ mesg = s["body"]
439
+
440
+ if nick == @nick
441
+ post nick, NOTICE, channel, mesg
442
+ else
443
+ message(nick, channel, mesg)
444
+ end
445
+ rescue Execepton => e
446
+ post server_name, NOTICE, channel, e.inspect
447
+ end
448
+ end
449
+ @channels[channel][:read] = @channels[channel][:read].last(100)
450
+ end
451
+ end
452
+
453
+ def generate_status_message(status)
454
+ s = status
455
+ mesg = s["text"]
456
+ @log.debug(mesg)
457
+
458
+ # added @user in no use @user reply message (Wassr only)
459
+ if s.has_key?("reply_status_url") and s["reply_status_url"] and s["text"] !~ /^@.*/ and %r{([^/]+)/statuses/[^/]+}.match(s["reply_status_url"])
460
+ reply_user_id = $1
461
+ mesg = "@#{reply_user_id} #{mesg}"
462
+ end
463
+ # display area name (Wassr only)
464
+ if s.has_key?("areaname") and s["areaname"]
465
+ mesg += " L: #{s["areaname"]}"
466
+ end
467
+ # display photo URL (Wassr only)
468
+ if s.has_key?("photo_url") and s["photo_url"]
469
+ mesg += " #{s["photo_url"]}"
470
+ end
471
+
472
+ # time = Time.parse(s["created_at"]) rescue Time.now
473
+ m = { "&quot;" => "\"", "&lt;"=> "<", "&gt;"=> ">", "&amp;"=> "&", "\n" => " "}
474
+ mesg.gsub!(/(#{m.keys.join("|")})/) { m[$1] }
475
+ mesg
476
+ end
477
+
478
+ def check_direct_messages
479
+ @prev_time_d ||= Time.now
480
+ api("direct_messages", {"since" => @prev_time_d.httpdate}).reverse_each do |s|
481
+ nick = s["sender_screen_name"]
482
+ mesg = s["text"]
483
+ time = Time.parse(s["created_at"])
484
+ @log.debug [nick, mesg, time].inspect
485
+ message(nick, @nick, mesg)
486
+ end
487
+ @prev_time_d = Time.now
488
+ end
489
+
490
+ def check_friends
491
+ first = true unless @friends
492
+ @friends ||= []
493
+ friends = api("statuses/friends")
494
+ if first && !@opts.key?("athack")
495
+ @friends = friends
496
+ post nil, RPL_NAMREPLY, @nick, "=", main_channel, @friends.map{|i| "@#{i["screen_name"]}" }.join(" ")
497
+ post nil, RPL_ENDOFNAMES, @nick, main_channel, "End of NAMES list"
498
+ else
499
+ prv_friends = @friends.map {|i| i["screen_name"] }
500
+ now_friends = friends.map {|i| i["screen_name"] }
501
+
502
+ # Twitter API bug?
503
+ return if !first && (now_friends.length - prv_friends.length).abs > 10
504
+
505
+ (now_friends - prv_friends).each do |join|
506
+ join = "@#{join}" if @opts.key?("athack")
507
+ post "#{join}!#{join}@#{api_base.host}", JOIN, main_channel
508
+ end
509
+ (prv_friends - now_friends).each do |part|
510
+ part = "@#{part}" if @opts.key?("athack")
511
+ post "#{part}!#{part}@#{api_base.host}", PART, main_channel, ""
512
+ end
513
+ @friends = friends
514
+ end
515
+ end
516
+
517
+ def freq(ratio)
518
+ ret = 3600 / (hourly_limit * ratio).round
519
+ @log.debug "Frequency: #{ret}"
520
+ ret
521
+ end
522
+
523
+ def start_jabber(jid, pass)
524
+ @log.info "Logging-in with #{jid} -> jabber_bot_id: #{jabber_bot_id}"
525
+ @im = Jabber::Simple.new(jid, pass)
526
+ @im.add(jabber_bot_id)
527
+ @im_thread = Thread.start do
528
+ loop do
529
+ begin
530
+ @im.received_messages.each do |msg|
531
+ @log.debug [msg.from, msg.body]
532
+ if msg.from.strip == jabber_bot_id
533
+ # Wassr -> 'nick(id): msg'
534
+ body = msg.body.sub(/^(.+?)(?:\((.+?)\))?: /, "")
535
+ if Regexp.last_match
536
+ nick, id = Regexp.last_match.captures
537
+ body = CGI.unescapeHTML(body)
538
+
539
+ case
540
+ when nick == "投稿完了"
541
+ log "#{nick}: #{body}"
542
+ when nick == "チャンネル投稿完了"
543
+ log "#{nick}: #{body}"
544
+ when body =~ /^#([a-z_]+)\s+(.+)$/i
545
+ # channel message or not
546
+ message(id || nick, "##{Regexp.last_match[1]}", Regexp.last_match[2])
547
+ when nick == "photo" && body =~ %r|^http://wassr\.jp/user/([^/]+)/|
548
+ nick = Regexp.last_match[1]
549
+ message(nick, main_channel, body)
550
+ else
551
+ @counters[nick] ||= 0
552
+ @counters[nick] += 1
553
+ tid = @tmap.push([nick, @counters[nick]])
554
+ message(nick, main_channel, "%s \x03%s [%s]" % [body, @opts["tid"], tid])
555
+ end
556
+ end
557
+ end
558
+ end
559
+ rescue Exception => e
560
+ @log.error "Error on Jabber loop: #{e.inspect}"
561
+ e.backtrace.each do |l|
562
+ @log.error "\t#{l}"
563
+ end
564
+ end
565
+ sleep 1
566
+ end
567
+ end
568
+ end
569
+
570
+ def require_post?(path)
571
+ [
572
+ "statuses/update",
573
+ "direct_messages/new",
574
+ "channel_message/update",
575
+ %r|^favorites/create|,
576
+ ].any? {|i| i === path }
577
+ end
578
+
579
+ def api(path, q={})
580
+ ret = {}
581
+ q["source"] ||= api_source
582
+
583
+ uri = api_base.dup
584
+ uri.path = "/#{path}.json"
585
+ uri.query = q.inject([]) {|r,(k,v)| v.inject(r) {|r,i| r << "#{k}=#{URI.escape(i, /[^-.!~*'()\w]/n)}" } }.join("&")
586
+
587
+
588
+ req = nil
589
+ if require_post?(path)
590
+ req = Net::HTTP::Post.new(uri.path)
591
+ req.body = uri.query
592
+ else
593
+ req = Net::HTTP::Get.new(uri.request_uri)
594
+ end
595
+ req.basic_auth(@real, @pass)
596
+ req["User-Agent"] = @user_agent
597
+ req["If-Modified-Since"] = q["since"] if q.key?("since")
598
+
599
+ @log.debug uri.inspect
600
+ ret = Net::HTTP.start(uri.host, uri.port) { |http| http.request(req) }
601
+
602
+ case ret
603
+ when Net::HTTPOK # 200
604
+ ret = JSON.parse(ret.body.gsub(/'(y(?:es)?|no?|true|false|null)'/, '"\1"'))
605
+ raise ApiFailed, "Server Returned Error: #{ret["error"]}" if ret.kind_of?(Hash) && ret["error"]
606
+ ret
607
+ when Net::HTTPNotModified # 304
608
+ []
609
+ when Net::HTTPBadRequest # 400
610
+ # exceeded the rate limitation
611
+ raise ApiFailed, "#{ret.code}: #{ret.message}"
612
+ else
613
+ raise ApiFailed, "Server Returned #{ret.code} #{ret.message}"
614
+ end
615
+ rescue Errno::ETIMEDOUT, JSON::ParserError, IOError, Timeout::Error, Errno::ECONNRESET => e
616
+ raise ApiFailed, e.inspect
617
+ end
618
+
619
+ def message(sender, target, str)
620
+ str = untinyurl(str)
621
+ sender = "#{sender}!#{sender}@#{api_base.host}"
622
+ post sender, PRIVMSG, target, str
623
+ end
624
+
625
+ def log(str)
626
+ str.gsub!(/\n/, " ")
627
+ post server_name, NOTICE, main_channel, str
628
+ end
629
+
630
+ def untinyurl(text)
631
+ text.gsub(%r|http://(preview\.)?tinyurl\.com/[0-9a-z=]+|i) {|m|
632
+ uri = URI(m)
633
+ uri.host = uri.host.sub($1, "") if $1
634
+ Net::HTTP.start(uri.host, uri.port) {|http|
635
+ http.open_timeout = 3
636
+ begin
637
+ http.head(uri.request_uri, { "User-Agent" => @user_agent })["Location"] || m
638
+ rescue Timeout::Error
639
+ m
640
+ end
641
+ }
642
+ }
643
+ end
644
+
645
+ class TypableMap < Hash
646
+ Roma = %w|k g ky gy s z sh j t d ch n ny h b p hy by py m my y r ry w v q|.unshift("").map {|consonant|
647
+ case
648
+ when consonant.size > 1, consonant == "y"
649
+ %w|a u o|
650
+ when consonant == "q"
651
+ %w|a i e o|
652
+ else
653
+ %w|a i u e o|
654
+ end.map {|vowel| "#{consonant}#{vowel}" }
655
+ }.flatten
656
+
657
+ def initialize(size=1)
658
+ @seq = Roma
659
+ @map = {}
660
+ @n = 0
661
+ @size = size
662
+ end
663
+
664
+ def generate(n)
665
+ ret = []
666
+ begin
667
+ n, r = n.divmod(@seq.size)
668
+ ret << @seq[r]
669
+ end while n > 0
670
+ ret.reverse.join
671
+ end
672
+
673
+ def push(obj)
674
+ id = generate(@n)
675
+ self[id] = obj
676
+ @n += 1
677
+ @n = @n % (@seq.size ** @size)
678
+ id
679
+ end
680
+ alias << push
681
+
682
+ def clear
683
+ @n = 0
684
+ super
685
+ end
686
+
687
+ private :[]=
688
+ undef update, merge, merge!, replace
689
+ end
690
+
691
+
89
692
  end
90
693
 
91
694
  if __FILE__ == $0
@@ -101,7 +704,7 @@ if __FILE__ == $0
101
704
 
102
705
  OptionParser.new do |parser|
103
706
  parser.instance_eval do
104
- self.banner = <<-EOB.gsub(/^\t+/, "")
707
+ self.banner = <<-EOB.gsub(/^\t+/, "")
105
708
  Usage: #{$0} [opts]
106
709
 
107
710
  EOB
@@ -131,7 +734,7 @@ if __FILE__ == $0
131
734
  opts[:foreground] = true
132
735
  end
133
736
 
134
- on("-n", "--name [login id]") do |name|
737
+ on("-n", "--name [user name or email address]") do |name|
135
738
  opts[:name] = name
136
739
  end
137
740
 
@@ -142,26 +745,29 @@ if __FILE__ == $0
142
745
  opts[:logger] = Logger.new(opts[:log], "daily")
143
746
  opts[:logger].level = opts[:debug] ? Logger::DEBUG : Logger::INFO
144
747
 
145
- def daemonize(foreground=false)
146
- trap("SIGINT") { exit! 0 }
147
- trap("SIGTERM") { exit! 0 }
148
- trap("SIGHUP") { exit! 0 }
149
- return yield if $DEBUG || foreground
150
- Process.fork do
151
- Process.setsid
152
- Dir.chdir "/"
153
- File.open("/dev/null") {|f|
154
- STDIN.reopen f
155
- STDOUT.reopen f
156
- STDERR.reopen f
157
- }
158
- yield
159
- end
160
- exit! 0
161
- end
162
-
163
- daemonize(opts[:debug] || opts[:foreground]) do
748
+ # def daemonize(foreground=false)
749
+ # trap("SIGINT") { exit! 0 }
750
+ # trap("SIGTERM") { exit! 0 }
751
+ # trap("SIGHUP") { exit! 0 }
752
+ # return yield if $DEBUG || foreground
753
+ # Process.fork do
754
+ # Process.setsid
755
+ # Dir.chdir "/"
756
+ # File.open("/dev/null") {|f|
757
+ # STDIN.reopen f
758
+ # STDOUT.reopen f
759
+ # STDERR.reopen f
760
+ # }
761
+ # yield
762
+ # end
763
+ # exit! 0
764
+ # end
765
+
766
+ # daemonize(opts[:debug] || opts[:foreground]) do
164
767
  Net::IRC::Server.new(opts[:host], opts[:port], WassrIrcGateway, opts).start
165
- end
768
+ # end
166
769
  end
167
770
 
771
+ # Local Variables:
772
+ # coding: utf-8
773
+ # End: