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

Revision 33907, 20.2 kB (checked in by drry, 3 weeks ago)
  • CTCP デコードで無視すべきバックスラッシュを除去するようにしました。
  • 正規表現のパターンを修正しました。
  • CTCP 判定を改善しました。
  • CTCP 分離メソッドを追加してみました。名前 (#ctcps) 等テキトーです。
  • 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" if RUBY_VERSION < "1.9" # 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[1].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                                if @utf7
262                                        message = Iconv.iconv("UTF-7", "UTF-8", message).join
263                                        message = message.force_encoding("ASCII-8BIT") if message.respond_to?(:force_encoding)
264                                end
265                                if !reply && @opts.key?("alwaysim") && @im && @im.connected? # in jabber mode, using jabber post
266                                        message = "##{channel} #{message}" unless "##{channel}" == main_channel
267                                        ret = @im.deliver(jabber_bot_id, message)
268                                        post "#{nick}!#{nick}@#{api_base.host}", TOPIC, channel, untinyurl(message)
269                                else
270                                        if "##{channel}" == main_channel
271                                                rid = rid_for(reply) if reply
272                                                ret = api("statuses/update", {"status" => message, "reply_status_rid" => rid})
273                                        else
274                                                ret = api("channel_message/update", {"name_en" => channel, "body" => message})
275                                        end
276                                        log "Status Updated via API"
277                                end
278                        else
279                                # direct message
280                                ret = api("direct_messages/new", {"user" => target, "text" => message})
281                        end
282                        raise ApiFailed, "API failed" unless ret
283                rescue => e
284                        @log.error [retry_count, e.inspect].inspect
285                        if retry_count > 0
286                                retry_count -= 1
287                                @log.debug "Retry to setting status..."
288                                retry
289                        else
290                                log "Some Error Happened on Sending #{message}. #{e}"
291                        end
292                end
293        end
294
295        def on_ctcp(target, message)
296                _, command, *args = message.split(/\s+/)
297                case command
298                when "utf7"
299                        begin
300                                require "iconv"
301                                @utf7 = !@utf7
302                                log "utf7 mode: #{@utf7 ? 'on' : 'off'}"
303                        rescue LoadError => e
304                                log "Can't load iconv."
305                        end
306                when "list"
307                        nick = args[0]
308                        @log.debug([ nick, message ])
309                        res = api("statuses/user_timeline", { "id" => nick }).reverse_each do |s|
310                                @log.debug(s)
311                                post nick, NOTICE, main_channel, "#{generate_status_message(s)}"
312                        end
313
314                        unless res
315                                post server_name, ERR_NOSUCHNICK, nick, "No such nick/channel"
316                        end
317                when "fav"
318                        target = args[0]
319                        st     = @tmap[target]
320                        id     = rid_for(target)
321                        if st || id
322                                unless id
323                                        if @im && @im.connected?
324                                                # IM のときはいろいろめんどうなことする
325                                                nick, count = *st
326                                                pos = @counters[nick] - count
327                                                @log.debug "%p %s %d/%d => %d" % [
328                                                        st,
329                                                        nick,
330                                                        count,
331                                                        @counters[nick],
332                                                        pos
333                                                ]
334                                                res = api("statuses/user_timeline", { "id" => nick })
335                                                raise ApiFailed, "#{nick} may be private mode" if res.empty?
336                                                if res[pos]
337                                                        id = res[pos]["rid"]
338                                                else
339                                                        raise ApiFailed, "#{pos} of #{nick} is not found."
340                                                end
341                                        else
342                                                id = st["id"] || st["rid"]
343                                        end
344                                end
345                                res = api("favorites/create/#{id}", {})
346                                post server_name, NOTICE, main_channel, "Fav: #{res["screen_name"]}: #{res["text"]}"
347                        else
348                                post server_name, NOTICE, main_channel, "No such id or status #{target}"
349                        end
350                when "link"
351                        tid = args[0]
352                        st  = @tmap[tid]
353                        if st
354                                st["link"] = (api_base + "/#{st["user"]["screen_name"]}/statuses/#{st["id"]}").to_s unless st["link"]
355                                post server_name, NOTICE, main_channel, st["link"]
356                        else
357                                post server_name, NOTICE, main_channel, "No such id #{tid}"
358                        end
359                end
360        rescue ApiFailed => e
361                log e.inspect
362        end; private :on_ctcp
363
364        def on_whois(m)
365                nick = m.params[0]
366                f = (@friends || []).find {|i| i["screen_name"] == nick }
367                if f
368                        post server_name, RPL_WHOISUSER,   @nick, nick, nick, api_base.host, "*", "#{f["name"]} / #{f["description"]}"
369                        post server_name, RPL_WHOISSERVER, @nick, nick, api_base.host, api_base.to_s
370                        post server_name, RPL_WHOISIDLE,   @nick, nick, "0", "seconds idle"
371                        post server_name, RPL_ENDOFWHOIS,  @nick, nick, "End of WHOIS list"
372                else
373                        post server_name, ERR_NOSUCHNICK, nick, "No such nick/channel"
374                end
375        end
376
377        def on_join(m)
378                channels = m.params[0].split(/\s*,\s*/)
379                channels.each do |channel|
380                        next if channel == main_channel
381                        res = api("channel/exists", { "name_en" => channel.sub(/^#/, "") })
382                        if res["exists"]
383                                @channels[channel] = {
384                                        :read => []
385                                }
386                                post "#{@nick}!#{@nick}@#{api_base.host}", JOIN, channel
387                        else
388                                post server_name, ERR_NOSUCHNICK, channel, "No such nick/channel"
389                        end
390                end
391        end
392
393        def on_part(m)
394                channel = m.params[0]
395                return if channel == main_channel
396                @channels.delete(channel)
397                post "#{@nick}!#{@nick}@#{api_base.host}", PART, channel
398        end
399
400        def on_who(m)
401                channel = m.params[0]
402                case
403                when channel == main_channel
404                        #     "<channel> <user> <host> <server> <nick>
405                        #         ( "H" / "G" > ["*"] [ ( "@" / "+" ) ]
406                        #             :<hopcount> <real name>"
407                        @friends.each do |f|
408                                user = nick = f["screen_name"]
409                                host = serv = api_base.host
410                                real = f["name"]
411                                post server_name, RPL_WHOREPLY, @nick, channel, user, host, serv, nick, "H*@", "0 #{real}"
412                        end
413                        post server_name, RPL_ENDOFWHO, @nick, channel
414                when @groups.key?(channel)
415                        @groups[channel].each do |name|
416                                f = @friends.find {|i| i["screen_name"] == name }
417                                user = nick = f["screen_name"]
418                                host = serv = api_base.host
419                                real = f["name"]
420                                post server_name, RPL_WHOREPLY, @nick, channel, user, host, serv, nick, "H*@", "0 #{real}"
421                        end
422                        post server_name, RPL_ENDOFWHO, @nick, channel
423                else
424                        post server_name, ERR_NOSUCHNICK, @nick, nick, "No such nick/channel"
425                end
426        end
427
428        private
429        def check_timeline
430                @prev_time ||= Time.at(0)
431                api("statuses/friends_timeline", {"since" => @prev_time.httpdate}).reverse_each do |s|
432                        id = s["id"] || s["rid"]
433                        next if id.nil? || @timeline.include?(id)
434                        @timeline << id
435                        nick = s["user_login_id"]
436                        mesg = generate_status_message(s)
437
438                        tid = @tmap.push(s)
439
440                        @log.debug [id, nick, mesg]
441                        if nick == @nick # 自分のときは topic に
442                                post "#{nick}!#{nick}@#{api_base.host}", TOPIC, main_channel, untinyurl(mesg)
443                        else
444                                if @opts["tid"]
445                                        message(nick, main_channel, "%s \x03%s [%s]" % [mesg, @opts["tid"], tid])
446                                else
447                                        message(nick, main_channel, "%s" % [mesg, tid])
448                                end
449                        end
450                end
451                @log.debug "@timeline.size = #{@timeline.size}"
452                @timeline  = @timeline.last(100)
453                @prev_time = Time.now
454        end
455
456        def check_channel
457                @channels.keys.each do |channel|
458                        @log.debug "getting channel -> #{channel}..."
459                        api("channel_message/list", { "name_en" => channel.sub(/^#/, "") }).reverse_each do |s|
460                                begin
461                                        id = Digest::MD5.hexdigest(s["user"]["login_id"] + s["body"])
462                                        next if @channels[channel][:read].include?(id)
463                                        @channels[channel][:read] << id
464                                        nick = s["user"]["login_id"]
465                                        mesg = s["body"]
466
467                                        if nick == @nick
468                                                post nick, NOTICE, channel, mesg
469                                        else
470                                                message(nick, channel, mesg)
471                                        end
472                                rescue Execepton => e
473                                        post server_name, NOTICE, channel, e.inspect
474                                end
475                        end
476                        @channels[channel][:read] = @channels[channel][:read].last(100)
477                end
478        end
479
480        def generate_status_message(status)
481                s = status
482                mesg = s["text"]
483                @log.debug(mesg)
484
485                begin
486                        require 'iconv'
487                        mesg = mesg.sub(/^.+ > |^.+/) {|str| Iconv.iconv("UTF-8", "UTF-7", str).join }
488                        mesg = "[utf7]: #{mesg}" if mesg =~ /[^a-z0-9\s]/i
489                rescue LoadError
490                rescue Iconv::IllegalSequence
491                end
492
493                # added @user in no use @user reply message (Wassr only)
494                if s.has_key?("reply_status_url") and s["reply_status_url"] and s["text"] !~ /^@.*/ and %r{([^/]+)/statuses/[^/]+}.match(s["reply_status_url"])
495                        reply_user_id = $1
496                        mesg = "@#{reply_user_id} #{mesg}"
497                end
498                # display area name (Wassr only)
499                if s.has_key?("areaname") and s["areaname"]
500                        mesg += " L: #{s["areaname"]}"
501                end
502                # display photo URL (Wassr only)
503                if s.has_key?("photo_url") and s["photo_url"]
504                        mesg += " #{s["photo_url"]}"
505                end
506
507                # time = Time.parse(s["created_at"]) rescue Time.now
508                m = { "&quot;" => "\"", "&lt;"=> "<", "&gt;"=> ">", "&amp;"=> "&", "\n" => " "}
509                mesg.gsub!(/(#{m.keys.join("|")})/) { m[$1] }
510                mesg
511        end
512
513        def check_direct_messages
514                @prev_time_d ||= Time.now
515                api("direct_messages", {"since" => @prev_time_d.httpdate}).reverse_each do |s|
516                        nick = s["sender_screen_name"]
517                        mesg = s["text"]
518                        time = Time.parse(s["created_at"])
519                        @log.debug [nick, mesg, time].inspect
520                        message(nick, @nick, mesg)
521                end
522                @prev_time_d = Time.now
523        end
524
525        def check_friends
526                first = true unless @friends
527                @friends ||= []
528                friends = []
529                1.upto(5) do |i|
530                        f = api("statuses/friends", {"page" => i.to_s})
531                        friends += f
532                        break if f.length < 100
533                end
534                if first && !@opts.key?("athack")
535                        @friends = friends
536                        post server_name, RPL_NAMREPLY,   @nick, "=", main_channel, @friends.map{|i| "@#{i["screen_name"]}" }.join(" ")
537                        post server_name, RPL_ENDOFNAMES, @nick, main_channel, "End of NAMES list"
538                else
539                        prv_friends = @friends.map {|i| i["screen_name"] }
540                        now_friends = friends.map {|i| i["screen_name"] }
541
542                        # Twitter API bug?
543                        return if !first && (now_friends.length - prv_friends.length).abs > 10
544
545                        (now_friends - prv_friends).each do |join|
546                                join = "@#{join}" if @opts.key?("athack")
547                                post "#{join}!#{join}@#{api_base.host}", JOIN, main_channel
548                        end
549                        (prv_friends - now_friends).each do |part|
550                                part = "@#{part}" if @opts.key?("athack")
551                                post "#{part}!#{part}@#{api_base.host}", PART, main_channel, ""
552                        end
553                        @friends = friends
554                end
555        end
556
557        def freq(ratio)
558                ret = 3600 / (hourly_limit * ratio).round
559                @log.debug "Frequency: #{ret}"
560                ret
561        end
562
563        def start_jabber(jid, pass)
564                @log.info "Logging-in with #{jid} -> jabber_bot_id: #{jabber_bot_id}"
565                @im = Jabber::Simple.new(jid, pass)
566                @im.add(jabber_bot_id)
567                @im_thread = Thread.start do
568                        loop do
569                                begin
570                                        @im.received_messages.each do |msg|
571                                                @log.debug [msg.from, msg.body]
572                                                if msg.from.strip == jabber_bot_id
573                                                        # Wassr -> 'nick(id): msg'
574                                                        body = msg.body.sub(/^(.+?)(?:\((.+?)\))?: /, "")
575                                                        if Regexp.last_match
576                                                                nick, id = Regexp.last_match.captures
577                                                                body = CGI.unescapeHTML(body)
578                                                                begin
579                                                                        require 'iconv'
580                                                                        body = body.sub(/^.+ > |^.+/) {|str| Iconv.iconv("UTF-8", "UTF-7", str).join }
581                                                                        body = "[utf7]: #{body}" if body =~ /[^a-z0-9\s]/i
582                                                                rescue LoadError
583                                                                rescue Iconv::IllegalSequence
584                                                                end
585
586                                                                case
587                                                                when nick == "投稿完了"
588                                                                        log "#{nick}: #{body}"
589                                                                when nick == "チャンネル投稿完了"
590                                                                        log "#{nick}: #{body}"
591                                                                when body =~ /^#([a-z_]+)\s+(.+)$/i
592                                                                        # channel message or not
593                                                                        message(id || nick, "##{Regexp.last_match[1]}", Regexp.last_match[2])
594                                                                when nick == "photo" && body =~ %r|^http://wassr\.jp/user/([^/]+)/|
595                                                                        nick = Regexp.last_match[1]
596                                                                        message(nick, main_channel, body)
597                                                                else
598                                                                        @counters[nick] ||= 0
599                                                                        @counters[nick] += 1
600                                                                        tid = @tmap.push([nick, @counters[nick]])
601                                                                        message(nick, main_channel, "%s \x03%s [%s]" % [body, @opts["tid"], tid])
602                                                                end
603                                                        end
604                                                end
605                                        end
606                                rescue Exception => e
607                                        @log.error "Error on Jabber loop: #{e.inspect}"
608                                        e.backtrace.each do |l|
609                                                @log.error "\t#{l}"
610                                        end
611                                end
612                                sleep 1
613                        end
614                end
615        end
616
617        def require_post?(path)
618                [
619                        "statuses/update",
620                        "direct_messages/new",
621                        "channel_message/update",
622                        %r|^favorites/create|,
623                ].any? {|i| i === path }
624        end
625
626        def api(path, q={})
627                ret           = {}
628                q["source"] ||= api_source
629
630                uri = api_base.dup
631                uri.path  = "/#{path}.json"
632                uri.query = q.inject([]) {|r,(k,v)| v ? r << "#{k}=#{URI.escape(v, /[^-.!~*'()\w]/n)}" : r }.join("&")
633
634
635                req = nil
636                if require_post?(path)
637                        req = Net::HTTP::Post.new(uri.path)
638                        req.body = uri.query
639                else
640                        req = Net::HTTP::Get.new(uri.request_uri)
641                end
642                req.basic_auth(@real, @pass)
643                req["User-Agent"]        = @user_agent
644                req["If-Modified-Since"] = q["since"] if q.key?("since")
645
646                @log.debug uri.inspect
647                ret = Net::HTTP.start(uri.host, uri.port) { |http| http.request(req) }
648
649                case ret
650                when Net::HTTPOK # 200
651                        ret = JSON.parse(ret.body)
652                        raise ApiFailed, "Server Returned Error: #{ret["error"]}" if ret.kind_of?(Hash) && ret["error"]
653                        ret
654                when Net::HTTPNotModified # 304
655                        []
656                when Net::HTTPBadRequest # 400
657                        # exceeded the rate limitation
658                        raise ApiFailed, "#{ret.code}: #{ret.message}"
659                else
660                        raise ApiFailed, "Server Returned #{ret.code} #{ret.message}"
661                end
662        rescue Errno::ETIMEDOUT, JSON::ParserError, IOError, Timeout::Error, Errno::ECONNRESET => e
663                raise ApiFailed, e.inspect
664        end
665
666        def message(sender, target, str)
667                str    = untinyurl(str)
668                sender = "#{sender}!#{sender}@#{api_base.host}"
669                post sender, PRIVMSG, target, str
670        end
671
672        def log(str)
673                str.gsub!(/\n/, " ")
674                post server_name, NOTICE, main_channel, str
675        end
676
677        def untinyurl(text)
678                text.gsub(%r|http://(preview\.)?tinyurl\.com/[0-9a-z=]+|i) {|m|
679                        uri = URI(m)
680                        uri.host = uri.host.sub($1, "") if $1
681                        Net::HTTP.start(uri.host, uri.port) {|http|
682                                http.open_timeout = 3
683                                begin
684                                        http.head(uri.request_uri, { "User-Agent" => @user_agent })["Location"] || m
685                                rescue Timeout::Error
686                                        m
687                                end
688                        }
689                }
690        end
691
692        # return rid of most recent matched status with text
693        def rid_for(text)
694                target = Regexp.new(Regexp.quote(text.strip), "i")
695                status = api("statuses/friends_timeline").find {|i|
696                        next false if i["user_login_id"] == @nick # 自分は除外
697                        i["text"] =~ target
698                }
699
700                @log.debug "Looking up status contains #{text.inspect} -> #{status.inspect}"
701                status ? status["rid"] : nil
702        end
703
704        class TypableMap < Hash
705                Roman = %w[
706                        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
707                ].unshift("").map do |consonant|
708                        case consonant
709                        when "y", /\A.{2}/ then %w|a u o|
710                        when "q"           then %w|a i e o|
711                        else                    %w|a i u e o|
712                        end.map {|vowel| "#{consonant}#{vowel}" }
713                end.flatten
714
715                def initialize(size = 1)
716                        @seq  = Roman
717                        @n    = 0
718                        @size = size
719                end
720
721                def generate(n)
722                        ret = []
723                        begin
724                                n, r = n.divmod(@seq.size)
725                                ret << @seq[r]
726                        end while n > 0
727                        ret.reverse.join
728                end
729
730                def push(obj)
731                        id = generate(@n)
732                        self[id] = obj
733                        @n += 1
734                        @n %= @seq.size ** @size
735                        id
736                end
737                alias << push
738
739                def clear
740                        @n = 0
741                        super
742                end
743
744                private :[]=
745                undef update, merge, merge!, replace
746        end
747
748
749end
750
751if __FILE__ == $0
752        require "optparse"
753
754        opts = {
755                :port  => 16670,
756                :host  => "localhost",
757                :log   => nil,
758                :debug => false,
759                :foreground => false,
760        }
761
762        OptionParser.new do |parser|
763                parser.instance_eval do
764                        self.banner = <<-EOB.gsub(/^\t+/, "")
765                                Usage: #{$0} [opts]
766
767                        EOB
768
769                        separator ""
770
771                        separator "Options:"
772                        on("-p", "--port [PORT=#{opts[:port]}]", "port number to listen") do |port|
773                                opts[:port] = port
774                        end
775
776                        on("-h", "--host [HOST=#{opts[:host]}]", "host name or IP address to listen") do |host|
777                                opts[:host] = host
778                        end
779
780                        on("-l", "--log LOG", "log file") do |log|
781                                opts[:log] = log
782                        end
783
784                        on("--debug", "Enable debug mode") do |debug|
785                                opts[:log]   = $stdout
786                                opts[:debug] = true
787                        end
788
789                        on("-f", "--foreground", "run foreground") do |foreground|
790                                opts[:log]        = $stdout
791                                opts[:foreground] = true
792                        end
793
794                        on("-n", "--name [user name or email address]") do |name|
795                                opts[:name] = name
796                        end
797
798                        parse!(ARGV)
799                end
800        end
801
802        opts[:logger] = Logger.new(opts[:log], "daily")
803        opts[:logger].level = opts[:debug] ? Logger::DEBUG : Logger::INFO
804
805#       def daemonize(foreground=false)
806#               trap("SIGINT")  { exit! 0 }
807#               trap("SIGTERM") { exit! 0 }
808#               trap("SIGHUP")  { exit! 0 }
809#               return yield if $DEBUG || foreground
810#               Process.fork do
811#                       Process.setsid
812#                       Dir.chdir "/"
813#                       File.open("/dev/null") {|f|
814#                               STDIN.reopen  f
815#                               STDOUT.reopen f
816#                               STDERR.reopen f
817#                       }
818#                       yield
819#               end
820#               exit! 0
821#       end
822
823#       daemonize(opts[:debug] || opts[:foreground]) do
824                Net::IRC::Server.new(opts[:host], opts[:port], WassrIrcGateway, opts).start
825#       end
826end
827
828# Local Variables:
829# coding: utf-8
830# End:
Note: See TracBrowser for help on using the browser.