net-irc 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: