root/lang/ruby/net-irc/trunk/examples/wig.rb

Revision 35515, 20.3 kB (checked in by cho45, 4 months ago)

ふつうにエラーっす

  • Property svn:executable set to *
Line 
1#!/usr/bin/env ruby
2# vim:fileencoding=UTF-8:
3=begin
4
5# wig.rb
6
7wig.rb channel: http://wassr.jp/channel/wigrb
8
9## Launch
10
11        $ ruby wig.rb
12
13If you want to help:
14
15        $ ruby wig.rb --help
16
17## Configuration
18
19Options specified by after irc realname.
20
21Configuration example for Tiarra ( http://coderepos.org/share/wiki/Tiarra ).
22
23        wassr {
24                host: localhost
25                port: 16670
26                name: username@example.com athack jabber=username@example.com:jabberpasswd tid=10 ratio=10:3:5
27                password: password on Wassr
28                in-encoding: utf8
29                out-encoding: utf8
30        }
31
32### athack
33
34If `athack` client option specified,
35all nick in join message is leading with @.
36
37So if you complemente nicks (e.g. Irssi),
38it's good for Twitter like reply command (@nick).
39
40In this case, you will see torrent of join messages after connected,
41because NAMES list can't send @ leading nick (it interpreted op.)
42
43### tid=<color>
44
45Apply 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
69### jabber=<jid>:<pass>
70
71If `jabber=<jid>:<pass>` option specified,
72use jabber to get friends timeline.
73
74You must setup im notifing settings in the site and
75install "xmpp4r-simple" gem.
76
77        $ sudo gem install xmpp4r-simple
78
79Be careful for managing password.
80
81### alwaysim
82
83Use IM instead of any APIs (e.g. post)
84
85### ratio=<timeline>:<friends>:<channel>
86
87## License
88
89Ruby's by cho45
90
91=end
92
93$LOAD_PATH << "lib"
94$LOAD_PATH << "../lib"
95
96$KCODE = "u" unless defined? ::Encoding # json use this
97
98require "rubygems"
99require "net/irc"
100require "net/http"
101require "uri"
102require "json"
103require "socket"
104require "time"
105require "logger"
106require "yaml"
107require "pathname"
108require "cgi"
109require "digest/md5"
110
111Net::HTTP.version_1_2
112
113class WassrIrcGateway < Net::IRC::Server::Session
114        def server_name
115                "wassrgw"
116        end
117
118        def server_version
119                "0.0.0"
120        end
121
122        def main_channel
123                "#wassr"
124        end
125
126        def api_base
127                URI("http://api.wassr.jp/")
128        end
129
130        def api_source
131                "wig.rb"
132        end
133
134        def jabber_bot_id
135                "wassr-bot@wassr.jp"
136        end
137
138        def hourly_limit
139                60
140        end
141
142        class ApiFailed < StandardError; end
143
144        def initialize(*args)
145                super
146                @channels   = {}
147                @user_agent = "#{self.class}/#{server_version} (wig.rb)"
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                @ratio   = Struct.new(:timeline, :friends, :channel).new(*(@opts["ratio"] || "10:3:5").split(":").map {|ratio| ratio.to_f })
185                @footing = @ratio.inject {|r,i| r + i }
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(@ratio[:friends] / @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(@ratio[:timeline] / @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(@ratio[:channel] / @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 m.ctcps.each {|ctcp| on_ctcp(m[0], ctcp) } 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                                reply   = message[/\s+>(.+)$/, 1]
261                                reply   = reply.force_encoding("UTF-8") if reply && reply.respond_to?(:force_encoding)
262                                if @utf7
263                                        message = Iconv.iconv("UTF-7", "UTF-8", message).join
264                                        message = message.force_encoding("ASCII-8BIT") if message.respond_to?(:force_encoding)
265                                end
266                                if !reply && @opts.key?("alwaysim") && @im && @im.connected? # in jabber mode, using jabber post
267                                        message = "##{channel} #{message}" unless "##{channel}" == main_channel
268                                        ret = @im.deliver(jabber_bot_id, message)
269                                        post "#{nick}!#{nick}@#{api_base.host}", TOPIC, channel, untinyurl(message)
270                                else
271                                        if "##{channel}" == main_channel
272                                                rid = rid_for(reply) if reply
273                                                ret = api("statuses/update", {"status" => message, "reply_status_rid" => rid})
274                                        else
275                                                ret = api("channel_message/update", {"name_en" => channel, "body" => message})
276                                        end
277                                        log "Status Updated via API"
278                                end
279                        else
280                                # direct message
281                                ret = api("direct_messages/new", {"user" => target, "text" => message})
282                        end
283                        raise ApiFailed, "API failed" unless ret
284                rescue => e
285                        @log.error [retry_count, e.inspect].inspect
286                        if retry_count > 0
287                                retry_count -= 1
288                                @log.debug "Retry to setting status..."
289                                retry
290                        else
291                                log "Some Error Happened on Sending #{message}. #{e}"
292                        end
293                end
294        end
295
296        def on_ctcp(target, message)
297                _, command, *args = message.split(/\s+/)
298                case command
299                when "utf7"
300                        begin
301                                require "iconv"
302                                @utf7 = !@utf7
303                                log "utf7 mode: #{@utf7 ? 'on' : 'off'}"
304                        rescue LoadError => e
305                                log "Can't load iconv."
306                        end
307                when "list"
308                        nick = args[0]
309                        @log.debug([ nick, message ])
310                        res = api("statuses/user_timeline", { "id" => nick }).reverse_each do |s|
311                                @log.debug(s)
312                                post nick, NOTICE, main_channel, "#{generate_status_message(s)}"
313                        end
314
315                        unless res
316                                post server_name, ERR_NOSUCHNICK, nick, "No such nick/channel"
317                        end
318                when "fav"
319                        target = args[0]
320                        st     = @tmap[target]
321                        id     = rid_for(target)
322                        if st || id
323                                unless id
324                                        if @im && @im.connected?
325                                                # IM のときはいろいろめんどうなことする
326                                                nick, count = *st
327                                                pos = @counters[nick] - count
328                                                @log.debug "%p %s %d/%d => %d" % [
329                                                        st,
330                                                        nick,
331                                                        count,
332                                                        @counters[nick],
333                                                        pos
334                                                ]
335                                                res = api("statuses/user_timeline", { "id" => nick })
336                                                raise ApiFailed, "#{nick} may be private mode" if res.empty?
337                                                if res[pos]
338                                                        id = res[pos]["rid"]
339                                                else
340                                                        raise ApiFailed, "#{pos} of #{nick} is not found."
341                                                end
342                                        else
343                                                id = st["id"] || st["rid"]
344                                        end
345                                end
346                                res = api("favorites/create/#{id}", {})
347                                post server_name, NOTICE, main_channel, "Fav: #{res["screen_name"]}: #{res["text"]}"
348                        else
349                                post server_name, NOTICE, main_channel, "No such id or status #{target}"
350                        end
351                when "link"
352                        tid = args[0]
353                        st  = @tmap[tid]
354                        if st
355                                st["link"] = (api_base + "/#{st["user"]["screen_name"]}/statuses/#{st["id"]}").to_s unless st["link"]
356                                post server_name, NOTICE, main_channel, st["link"]
357                        else
358                                post server_name, NOTICE, main_channel, "No such id #{tid}"
359                        end
360                end
361        rescue ApiFailed => e
362                log e.inspect
363        end; private :on_ctcp
364
365        def on_whois(m)
366                nick = m.params[0]
367                f = (@friends || []).find {|i| i["screen_name"] == nick }
368                if f
369                        post server_name, RPL_WHOISUSER,   @nick, nick, nick, api_base.host, "*", "#{f["name"]} / #{f["description"]}"
370                        post server_name, RPL_WHOISSERVER, @nick, nick, api_base.host, api_base.to_s
371                        post server_name, RPL_WHOISIDLE,   @nick, nick, "0", "seconds idle"
372                        post server_name, RPL_ENDOFWHOIS,  @nick, nick, "End of WHOIS list"
373                else
374                        post server_name, ERR_NOSUCHNICK, nick, "No such nick/channel"
375                end
376        end
377
378        def on_join(m)
379                channels = m.params[0].split(/\s*,\s*/)
380                channels.each do |channel|
381                        next if channel == main_channel
382                        res = api("channel/exists", { "name_en" => channel.sub(/^#/, "") })
383                        if res["exists"]
384                                @channels[channel] = {
385                                        :read => []
386                                }
387                                post "#{@nick}!#{@nick}@#{api_base.host}", JOIN, channel
388                        else
389                                post server_name, ERR_NOSUCHNICK, channel, "No such nick/channel"
390                        end
391                end
392        end
393
394        def on_part(m)
395                channel = m.params[0]
396                return if channel == main_channel
397                @channels.delete(channel)
398                post "#{@nick}!#{@nick}@#{api_base.host}", PART, channel
399        end
400
401        def on_who(m)
402                channel = m.params[0]
403                case
404                when channel == main_channel
405                        #     "<channel> <user> <host> <server> <nick>
406                        #         ( "H" / "G" > ["*"] [ ( "@" / "+" ) ]
407                        #             :<hopcount> <real name>"
408                        @friends.each do |f|
409                                user = nick = f["screen_name"]
410                                host = serv = api_base.host
411                                real = f["name"]
412                                post server_name, RPL_WHOREPLY, @nick, channel, user, host, serv, nick, "H*@", "0 #{real}"
413                        end
414                        post server_name, RPL_ENDOFWHO, @nick, channel
415                when @groups.key?(channel)
416                        @groups[channel].each do |name|
417                                f = @friends.find {|i| i["screen_name"] == name }
418                                user = nick = f["screen_name"]
419                                host = serv = api_base.host
420                                real = f["name"]
421                                post server_name, RPL_WHOREPLY, @nick, channel, user, host, serv, nick, "H*@", "0 #{real}"
422                        end
423                        post server_name, RPL_ENDOFWHO, @nick, channel
424                else
425                        post server_name, ERR_NOSUCHNICK, @nick, nick, "No such nick/channel"
426                end
427        end
428
429        private
430        def check_timeline
431                @prev_time ||= Time.at(0)
432                api("statuses/friends_timeline", {"since" => @prev_time.httpdate}).reverse_each do |s|
433                        id = s["id"] || s["rid"]
434                        next if id.nil? || @timeline.include?(id)
435                        @timeline << id
436                        nick = s["user_login_id"]
437                        mesg = generate_status_message(s)
438
439                        tid = @tmap.push(s)
440
441                        @log.debug [id, nick, mesg]
442                        if nick == @nick # 自分のときは topic に
443                                post "#{nick}!#{nick}@#{api_base.host}", TOPIC, main_channel, untinyurl(mesg)
444                        else
445                                if @opts["tid"]
446                                        message(nick, main_channel, "%s \x03%s [%s]" % [mesg, @opts["tid"], tid])
447                                else
448                                        message(nick, main_channel, "%s" % [mesg, tid])
449                                end
450                        end
451                end
452                @log.debug "@timeline.size = #{@timeline.size}"
453                @timeline  = @timeline.last(100)
454                @prev_time = Time.now
455        end
456
457        def check_channel
458                @channels.keys.each do |channel|
459                        @log.debug "getting channel -> #{channel}..."
460                        api("channel_message/list", { "name_en" => channel.sub(/^#/, "") }).reverse_each do |s|
461                                begin
462                                        id = Digest::MD5.hexdigest(s["user"]["login_id"] + s["body"])
463                                        next if @channels[channel][:read].include?(id)
464                                        @channels[channel][:read] << id
465                                        nick = s["user"]["login_id"]
466                                        mesg = s["body"]
467
468                                        if nick == @nick
469                                                post nick, NOTICE, channel, mesg
470                                        else
471                                                message(nick, channel, mesg)
472                                        end
473                                rescue Execepton => e
474                                        post server_name, NOTICE, channel, e.inspect
475                                end
476                        end
477                        @channels[channel][:read] = @channels[channel][:read].last(100)
478                end
479        end
480
481        def generate_status_message(status)
482                s = status
483                mesg = s["text"]
484                @log.debug(mesg)
485
486                begin
487                        require 'iconv'
488                        mesg = mesg.sub(/^.+ > |^.+/) {|str| Iconv.iconv("UTF-8", "UTF-7", str).join }
489                        mesg = "[utf7]: #{mesg}" if mesg =~ /[^a-z0-9\s]/i
490                rescue LoadError
491                rescue Iconv::IllegalSequence
492                end
493
494                # added @user in no use @user reply message (Wassr only)
495                if s.has_key?("reply_status_url") and s["reply_status_url"] and s["text"] !~ /^@.*/ and %r{([^/]+)/statuses/[^/]+}.match(s["reply_status_url"])
496                        reply_user_id = $1
497                        mesg = "@#{reply_user_id} #{mesg}"
498                end
499                # display area name (Wassr only)
500                if s.has_key?("areaname") and s["areaname"]
501                        mesg += " L: #{s["areaname"]}"
502                end
503                # display photo URL (Wassr only)
504                if s.has_key?("photo_url") and s["photo_url"]
505                        mesg += " #{s["photo_url"]}"
506                end
507
508                # time = Time.parse(s["created_at"]) rescue Time.now
509                m = { "&quot;" => "\"", "&lt;"=> "<", "&gt;"=> ">", "&amp;"=> "&", "\n" => " "}
510                mesg.gsub!(/(#{m.keys.join("|")})/) { m[$1] }
511                mesg
512        end
513
514        def check_direct_messages
515                @prev_time_d ||= Time.now
516                api("direct_messages", {"since" => @prev_time_d.httpdate}).reverse_each do |s|
517                        nick = s["sender_screen_name"]
518                        mesg = s["text"]
519                        time = Time.parse(s["created_at"])
520                        @log.debug [nick, mesg, time].inspect
521                        message(nick, @nick, mesg)
522                end
523                @prev_time_d = Time.now
524        end
525
526        def check_friends
527                first = true unless @friends
528                @friends ||= []
529                friends = []
530                1.upto(5) do |i|
531                        f = api("statuses/friends", {"page" => i.to_s})
532                        friends += f
533                        break if f.length < 100
534                end
535                if first && !@opts.key?("athack")
536                        @friends = friends
537                        post server_name, RPL_NAMREPLY,   @nick, "=", main_channel, @friends.map{|i| "@#{i["screen_name"]}" }.join(" ")
538                        post server_name, RPL_ENDOFNAMES, @nick, main_channel, "End of NAMES list"
539                else
540                        prv_friends = @friends.map {|i| i["screen_name"] }
541                        now_friends = friends.map {|i| i["screen_name"] }
542
543                        # Twitter API bug?
544                        return if !first && (now_friends.length - prv_friends.length).abs > 10
545
546                        (now_friends - prv_friends).each do |join|
547                                join = "@#{join}" if @opts.key?("athack")
548                                post "#{join}!#{join}@#{api_base.host}", JOIN, main_channel
549                        end
550                        (prv_friends - now_friends).each do |part|
551                                part = "@#{part}" if @opts.key?("athack")
552                                post "#{part}!#{part}@#{api_base.host}", PART, main_channel, ""
553                        end
554                        @friends = friends
555                end
556        end
557
558        def freq(ratio)
559                ret = 3600 / (hourly_limit * ratio).round
560                @log.debug "Frequency: #{ret}"
561                ret
562        end
563
564        def start_jabber(jid, pass)
565                @log.info "Logging-in with #{jid} -> jabber_bot_id: #{jabber_bot_id}"
566                @im = Jabber::Simple.new(jid, pass)
567                @im.add(jabber_bot_id)
568                @im_thread = Thread.start do
569                        loop do
570                                begin
571                                        @im.received_messages.each do |msg|
572                                                @log.debug [msg.from, msg.body]
573                                                if msg.from.strip == jabber_bot_id
574                                                        # Wassr -> 'nick(id): msg'
575                                                        body = msg.body.sub(/^(.+?)(?:\((.+?)\))?: /, "")
576                                                        if Regexp.last_match
577                                                                nick, id = Regexp.last_match.captures
578                                                                body = CGI.unescapeHTML(body)
579                                                                begin
580                                                                        require 'iconv'
581                                                                        body = body.sub(/^.+ > |^.+/) {|str| Iconv.iconv("UTF-8", "UTF-7", str).join }
582                                                                        body = "[utf7]: #{body}" if body =~ /[^a-z0-9\s]/i
583                                                                rescue LoadError
584                                                                rescue Iconv::IllegalSequence
585                                                                end
586
587                                                                case
588                                                                when nick == "投稿完了"
589                                                                        log "#{nick}: #{body}"
590                                                                when nick == "チャンネル投稿完了"
591                                                                        log "#{nick}: #{body}"
592                                                                when body =~ /^#([a-z_]+)\s+(.+)$/i
593                                                                        # channel message or not
594                                                                        message(id || nick, "##{Regexp.last_match[1]}", Regexp.last_match[2])
595                                                                when nick == "photo" && body =~ %r|^http://wassr\.jp/user/([^/]+)/|
596                                                                        nick = Regexp.last_match[1]
597                                                                        message(nick, main_channel, body)
598                                                                else
599                                                                        @counters[nick] ||= 0
600                                                                        @counters[nick] += 1
601                                                                        tid = @tmap.push([nick, @counters[nick]])
602                                                                        message(nick, main_channel, "%s \x03%s [%s]" % [body, @opts["tid"], tid])
603                                                                end
604                                                        end
605                                                end
606                                        end
607                                rescue Exception => e
608                                        @log.error "Error on Jabber loop: #{e.inspect}"
609                                        e.backtrace.each do |l|
610                                                @log.error "\t#{l}"
611                                        end
612                                end
613                                sleep 1
614                        end
615                end
616        end
617
618        def require_post?(path)
619                [
620                        "statuses/update",
621                        "direct_messages/new",
622                        "channel_message/update",
623                        %r|^favorites/create|,
624                ].any? {|i| i === path }
625        end
626
627        def api(path, q={})
628                ret           = {}
629                q["source"] ||= api_source
630
631                uri = api_base.dup
632                uri.path  = "/#{path}.json"
633                uri.query = q.inject([]) {|r,(k,v)| v ? r << "#{k}=#{URI.escape(v, /[^-.!~*'()\w]/n)}" : r }.join("&")
634
635
636                req = nil
637                if require_post?(path)
638                        req = Net::HTTP::Post.new(uri.path)
639                        req.body = uri.query
640                else
641                        req = Net::HTTP::Get.new(uri.request_uri)
642                end
643                req.basic_auth(@real, @pass)
644                req["User-Agent"]        = @user_agent
645                req["If-Modified-Since"] = q["since"] if q.key?("since")
646
647                @log.debug uri.inspect
648                ret = Net::HTTP.start(uri.host, uri.port) { |http| http.request(req) }
649
650                case ret
651                when Net::HTTPOK # 200
652                        ret = JSON.parse(ret.body)
653                        raise ApiFailed, "Server Returned Error: #{ret["error"]}" if ret.kind_of?(Hash) && ret["error"]
654                        ret
655                when Net::HTTPNotModified # 304
656                        []
657                when Net::HTTPBadRequest # 400
658                        # exceeded the rate limitation
659                        raise ApiFailed, "#{ret.code}: #{ret.message}"
660                else
661                        raise ApiFailed, "Server Returned #{ret.code} #{ret.message}"
662                end
663        rescue Errno::ETIMEDOUT, JSON::ParserError, IOError, Timeout::Error, Errno::ECONNRESET => e
664                raise ApiFailed, e.inspect
665        end
666
667        def message(sender, target, str)
668                str    = untinyurl(str)
669                sender = "#{sender}!#{sender}@#{api_base.host}"
670                post sender, PRIVMSG, target, str
671        end
672
673        def log(str)
674                str.gsub!(/\n/, " ")
675                post server_name, NOTICE, main_channel, str
676        end
677
678        def untinyurl(text)
679                text.gsub(%r|http://(preview\.)?tinyurl\.com/[0-9a-z=]+|i) {|m|
680                        uri = URI(m)
681                        uri.host = uri.host.sub($1, "") if $1
682                        Net::HTTP.start(uri.host, uri.port) {|http|
683                                http.open_timeout = 3
684                                begin
685                                        http.head(uri.request_uri, { "User-Agent" => @user_agent })["Location"] || m
686                                rescue Timeout::Error
687                                        m
688                                end
689                        }
690                }
691        end
692
693        # return rid of most recent matched status with text
694        def rid_for(text)
695                target = Regexp.new(Regexp.quote(text.strip), "i")
696                status = api("statuses/friends_timeline").find {|i|
697                        next false if i["user_login_id"] == @nick # 自分は除外
698                        i["text"] =~ target
699                }
700
701                @log.debug "Looking up status contains #{text.inspect} -> #{status.inspect}"
702                status ? status["rid"] : nil
703        end
704
705        class TypableMap < Hash
706                Roman = %w[
707                        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
708                ].unshift("").map do |consonant|
709                        case consonant
710                        when "y", /\A.{2}/ then %w|a u o|
711                        when "q"           then %w|a i e o|
712                        else                    %w|a i u e o|
713                        end.map {|vowel| "#{consonant}#{vowel}" }
714                end.flatten
715
716                def initialize(size = 1)
717                        @seq  = Roman
718                        @n    = 0
719                        @size = size
720                end
721
722                def generate(n)
723                        ret = []
724                        begin
725                                n, r = n.divmod(@seq.size)
726                                ret << @seq[r]
727                        end while n > 0
728                        ret.reverse.join
729                end
730
731                def push(obj)
732                        id = generate(@n)
733                        self[id] = obj
734                        @n += 1
735                        @n %= @seq.size ** @size
736                        id
737                end
738                alias << push
739
740                def clear
741                        @n = 0
742                        super
743                end
744
745                private :[]=
746                undef update, merge, merge!, replace
747        end
748
749
750end
751
752if __FILE__ == $0
753        require "optparse"
754
755        opts = {
756                :port  => 16670,
757                :host  => "localhost",
758                :log   => nil,
759                :debug => false,
760                :foreground => false,
761        }
762
763        OptionParser.new do |parser|
764                parser.instance_eval do
765                        self.banner = <<-EOB.gsub(/^\t+/, "")
766                                Usage: #{$0} [opts]
767
768                        EOB
769
770                        separator ""
771
772                        separator "Options:"
773                        on("-p", "--port [PORT=#{opts[:port]}]", "port number to listen") do |port|
774                                opts[:port] = port
775                        end
776
777                        on("-h", "--host [HOST=#{opts[:host]}]", "host name or IP address to listen") do |host|
778                                opts[:host] = host
779                        end
780
781                        on("-l", "--log LOG", "log file") do |log|
782                                opts[:log] = log
783                        end
784
785                        on("--debug", "Enable debug mode") do |debug|
786                                opts[:log]   = $stdout
787                                opts[:debug] = true
788                        end
789
790                        on("-f", "--foreground", "run foreground") do |foreground|
791                                opts[:log]        = $stdout
792                                opts[:foreground] = true
793                        end
794
795                        on("-n", "--name [user name or email address]") do |name|
796                                opts[:name] = name
797                        end
798
799                        parse!(ARGV)
800                end
801        end
802
803        opts[:logger] = Logger.new(opts[:log], "daily")
804        opts[:logger].level = opts[:debug] ? Logger::DEBUG : Logger::INFO
805
806#       def daemonize(foreground=false)
807#               trap("SIGINT")  { exit! 0 }
808#               trap("SIGTERM") { exit! 0 }
809#               trap("SIGHUP")  { exit! 0 }
810#               return yield if $DEBUG || foreground
811#               Process.fork do
812#                       Process.setsid
813#                       Dir.chdir "/"
814#                       File.open("/dev/null") {|f|
815#                               STDIN.reopen  f
816#                               STDOUT.reopen f
817#                               STDERR.reopen f
818#                       }
819#                       yield
820#               end
821#               exit! 0
822#       end
823
824#       daemonize(opts[:debug] || opts[:foreground]) do
825                Net::IRC::Server.new(opts[:host], opts[:port], WassrIrcGateway, opts).start
826#       end
827end
828
829# Local Variables:
830# coding: utf-8
831# End:
Note: See TracBrowser for help on using the browser.