Changeset 34249

Show
Ignore:
Timestamp:
07/03/09 00:37:18 (8 months ago)
Author:
drry
Message:
  • (tig.rb) Twitter の制限緩和に合わせて既定の API 利用比率を変更しました。(20090702T1025+09)
  • (tig.rb) 配置換え。大クラス、いろいろカオスってきた。
Files:
1 modified

Legend:

Unmodified
Added
Removed
  • lang/ruby/net-irc/trunk/examples/tig.rb

    r34198 r34249  
    4848                #   (60, 360 and 150 seconds) 
    4949                #name: username dm ratio=30:5:12 maxlimit=94 mentions 
     50                # 
     51                # <http://cheebow.info/chemt/archives/2009/07/api150rhtwit.html> 
     52                #   (36, 360 and 150 seconds) 
     53                #name: username dm ratio=50:5:12 maxlimit=134 mentions 
    5054                # 
    5155                # for Jabber 
     
    107111### ratio=<timeline>:<dm>[:<mentions>] 
    108112 
    109 "80:3:15" by default. 
     113"121:6:20" by default. 
    110114 
    111115        /me ratios 
    112116 
    113            ratio | timeline |   dm  | mentions | 
     117           Ratio | Timeline |   DM  | Mentions | 
    114118        ---------+----------+-------+----------| 
    115                1 |      37s |   N/A |      N/A | 
    116             46:3 |      39s |   10m OR N/A     | 
    117             43:6 |      42s |    5m OR N/A     | 
    118           43:3:3 |      42s |   10m |      10m | 
     119               1 |      24s |   N/A |      N/A | 
     120           141:6 |      26s |   10m OR N/A     | 
     121          135:12 |      27s |    5m OR N/A     | 
     122         135:6:6 |      27s |   10m |      10m | 
    119123        ---------+----------+-------+----------| 
    120          80:3:15 |      45s |   20m |       4m | 
     124        121:6:20 |      30s |   10m |       3m | 
    121125        ---------+----------+-------+----------| 
    122              4:1 |      46s |  3m4s |      N/A | 
     126             4:1 |      31s |  2m1s |      N/A | 
     127         50:5:12 |      49s | 8m12s |    3m25s | 
    123128          20:5:6 |      57s | 3m48s |    3m10s | 
    124129         30:5:12 |      58s | 5m45s |    2m24s | 
    125          31:4:15 |       1m | 7m30s |       2m | 
    126            1:1:1 |    1m50s | 1m50s |    1m50s | 
     130           1:1:1 |    1m13s | 1m13s |    1m13s | 
    127131        ---------------------------------------+ 
     132                            (Hourly limit: 150) 
    128133 
    129134### dm[=<ratio>] 
     
    285290 
    286291        def hourly_limit 
    287                 100 
     292                150 
    288293        end 
    289294 
     
    392397                end if @opts.tid 
    393398 
    394                 @ratio = (@opts.ratio || "80").split(":") 
     399                @ratio = (@opts.ratio || "121").split(":") 
    395400                @ratio = Struct.new(:timeline, :dm, :mentions).new(*@ratio) 
    396                 @ratio.dm       ||= @opts.dm == true ? @opts.mentions ?  3 : 18 : @opts.dm 
    397                 @ratio.mentions ||= @opts.mentions == true ? @opts.dm ? 15 : 18 : @opts.mentions 
     401                @ratio.dm       ||= @opts.dm == true ? @opts.mentions ?  6 : 26 : @opts.dm 
     402                @ratio.mentions ||= @opts.mentions == true ? @opts.dm ? 20 : 26 : @opts.mentions 
    398403 
    399404                @check_friends_thread = Thread.start do 
     
    594599        end 
    595600 
     601        def on_whois(m) 
     602                nick = m.params[0] 
     603                unless nick.nick? 
     604                        post server_name, ERR_NOSUCHNICK, nick, "No such nick/channel" 
     605                        return 
     606                end 
     607 
     608                unless user = user(nick) 
     609                        if api("users/username_available", { :username => nick }).valid 
     610                        # TODO: 404 suspended 
     611                                post server_name, ERR_NOSUCHNICK, nick, "No such nick/channel" 
     612                                return 
     613                        end 
     614                        user = api("users/show/#{nick}", {}, { :authenticate => false }) 
     615                end 
     616 
     617                prefix    = prefix(user) 
     618                desc      = user.name 
     619                desc      = "#{desc} / #{user.description}".gsub(/\s+/, " ") if user.description and not user.description.empty? 
     620                signon_at = Time.parse(user.created_at).to_i rescue 0 
     621                idle_sec  = (Time.now - (user.status ? Time.parse(user.status.created_at) : signon_at)).to_i rescue 0 
     622                location  = user.location 
     623                location  = "SoMa neighborhood of San Francisco, CA" if location.nil? or location.empty? 
     624                post server_name, RPL_WHOISUSER,   @nick, nick, prefix.user, prefix.host, "*", desc 
     625                post server_name, RPL_WHOISSERVER, @nick, nick, api_base.host, location 
     626                post server_name, RPL_WHOISIDLE,   @nick, nick, "#{idle_sec}", "#{signon_at}", "seconds idle, signon time" 
     627                post server_name, RPL_ENDOFWHOIS,  @nick, nick, "End of WHOIS list" 
     628                if @drones.include?(user.id) 
     629                        post server_name, RPL_WHOISBOT, @nick, nick, "is a \002Bot\002 on #{server_name}" 
     630                end 
     631        end 
     632 
     633        def on_who(m) 
     634                channel = m.params[0] 
     635                case 
     636                when channel.casecmp(main_channel).zero? 
     637                        users = [@me] 
     638                        users.concat @friends.reverse if @friends 
     639                        users.each {|friend| whoreply channel, friend } 
     640                        post server_name, RPL_ENDOFWHO, @nick, channel 
     641                when (@groups.key?(channel) and @friends) 
     642                        @groups[channel].each do |nick| 
     643                                whoreply channel, friend(nick) 
     644                        end 
     645                        post server_name, RPL_ENDOFWHO, @nick, channel 
     646                else 
     647                        post server_name, ERR_NOSUCHNICK, @nick, "No such nick/channel" 
     648                end 
     649        end 
     650 
     651        def on_join(m) 
     652                channels = m.params[0].split(/ *, */) 
     653                channels.each do |channel| 
     654                        channel = channel.split(" ", 2).first 
     655                        next if channel.casecmp(main_channel).zero? 
     656 
     657                        @channels << channel 
     658                        @channels.uniq! 
     659                        post @prefix, JOIN, channel 
     660                        post server_name, MODE, channel, "+mtio", @nick 
     661                        save_config 
     662                end 
     663        end 
     664 
     665        def on_part(m) 
     666                channel = m.params[0] 
     667                return if channel.casecmp(main_channel).zero? 
     668 
     669                @channels.delete(channel) 
     670                post @prefix, PART, channel, "Ignore group #{channel}, but setting is alive yet." 
     671        end 
     672 
     673        def on_invite(m) 
     674                nick, channel = *m.params 
     675                if not nick.nick? or @nick.casecmp(nick).zero? 
     676                        post server_name, ERR_NOSUCHNICK, nick, "No such nick/channel" # or yourself 
     677                        return 
     678                end 
     679 
     680                friend = friend(nick) 
     681 
     682                case 
     683                when channel.casecmp(main_channel).zero? 
     684                        case 
     685                        when friend #TODO 
     686                        when api("users/username_available", { :username => nick }).valid 
     687                                post server_name, ERR_NOSUCHNICK, nick, "No such nick/channel" 
     688                        else 
     689                                user = api("friendships/create/#{nick}") 
     690                                join main_channel, [user] 
     691                                @friends << user if @friends 
     692                                @me.friends_count += 1 
     693                        end 
     694                when friend 
     695                        ((@groups[channel] ||= []) << friend.screen_name).uniq! 
     696                        join channel, [friend] 
     697                        save_config 
     698                else 
     699                        post server_name, ERR_NOSUCHNICK, nick, "No such nick/channel" 
     700                end 
     701        end 
     702 
     703        def on_kick(m) 
     704                channel, nick, msg = *m.params 
     705 
     706                if channel.casecmp(main_channel).zero? 
     707                        @friends.delete_if do |friend| 
     708                                if friend.screen_name.casecmp(nick).zero? 
     709                                        user = api("friendships/destroy/#{friend.id}") 
     710                                        if user.is_a? User 
     711                                                post prefix(user), PART, main_channel, "Removed: #{msg}" 
     712                                                @me.friends_count -= 1 
     713                                        end 
     714                                end 
     715                        end if @friends 
     716                else 
     717                        friend = friend(nick) 
     718                        if friend 
     719                                (@groups[channel] ||= []).delete(friend.screen_name) 
     720                                post prefix(friend), PART, channel, "Removed: #{msg}" 
     721                                save_config 
     722                        else 
     723                                post server_name, ERR_NOSUCHNICK, nick, "No such nick/channel" 
     724                        end 
     725                end 
     726        end 
     727 
     728        #def on_nick(m) 
     729        #       @nicknames[@nick] = m.params[0] 
     730        #end 
     731 
     732        def on_topic(m) 
     733                channel = m.params[0] 
     734                return if not channel.casecmp(main_channel).zero? or @me.status.nil? 
     735 
     736                begin 
     737                        require "levenshtein" 
     738                        topic    = m.params[1] 
     739                        previous = @me.status 
     740                        return unless previous 
     741 
     742                        distance = Levenshtein.normalized_distance(previous.text, topic) 
     743                        return if distance.zero? 
     744 
     745                        status = api("statuses/update", { :status => topic, :source => source }) 
     746                        log oops(ret) if status.truncated 
     747                        status.user.status = status 
     748                        @me = status.user 
     749 
     750                        if distance < 0.5 
     751                                deleted = api("statuses/destroy/#{previous.id}") 
     752                                @timeline.delete_if {|tid, s| s.id == deleted.id } 
     753                                log "Fixed: #{status.text}" 
     754                        else 
     755                                log "Status updated" 
     756                        end 
     757                rescue LoadError 
     758                end 
     759        end 
     760 
     761        def on_mode(m) 
     762                channel = m.params[0] 
     763 
     764                unless m.params[1] 
     765                        if channel.ch? 
     766                                mode = "+mt" 
     767                                mode += "i" unless channel.casecmp(main_channel).zero? 
     768                                post server_name, RPL_CHANNELMODEIS, @nick, channel, mode 
     769                                #post server_name, RPL_CREATEONTIME, @nick, channel, 0 
     770                        elsif channel.casecmp(@nick).zero? 
     771                                post server_name, RPL_UMODEIS, @nick, @nick, "+o" 
     772                        end 
     773                end 
     774        end 
     775 
     776        private 
    596777        def on_ctcp(target, mesg) 
    597778                type, mesg = mesg.split(" ", 2) 
    598779                method = "on_ctcp_#{type.downcase}".to_sym 
    599780                send(method, target, mesg) if respond_to? method, true 
    600         end; private :on_ctcp 
     781        end 
    601782 
    602783        def on_ctcp_action(target, mesg) 
     
    8831064        rescue APIFailed => e 
    8841065                log e.inspect 
    885         end; private :on_ctcp_action 
    886  
    887         def on_whois(m) 
    888                 nick = m.params[0] 
    889                 unless nick.nick? 
    890                         post server_name, ERR_NOSUCHNICK, nick, "No such nick/channel" 
    891                         return 
    892                 end 
    893  
    894                 unless user = user(nick) 
    895                         if api("users/username_available", { :username => nick }).valid 
    896                         # TODO: 404 suspended 
    897                                 post server_name, ERR_NOSUCHNICK, nick, "No such nick/channel" 
    898                                 return 
    899                         end 
    900                         user = api("users/show/#{nick}", {}, { :authenticate => false }) 
    901                 end 
    902  
    903                 prefix    = prefix(user) 
    904                 desc      = user.name 
    905                 desc      = "#{desc} / #{user.description}".gsub(/\s+/, " ") if user.description and not user.description.empty? 
    906                 signon_at = Time.parse(user.created_at).to_i rescue 0 
    907                 idle_sec  = (Time.now - (user.status ? Time.parse(user.status.created_at) : signon_at)).to_i rescue 0 
    908                 location  = user.location 
    909                 location  = "SoMa neighborhood of San Francisco, CA" if location.nil? or location.empty? 
    910                 post server_name, RPL_WHOISUSER,   @nick, nick, prefix.user, prefix.host, "*", desc 
    911                 post server_name, RPL_WHOISSERVER, @nick, nick, api_base.host, location 
    912                 post server_name, RPL_WHOISIDLE,   @nick, nick, "#{idle_sec}", "#{signon_at}", "seconds idle, signon time" 
    913                 post server_name, RPL_ENDOFWHOIS,  @nick, nick, "End of WHOIS list" 
    914                 if @drones.include?(user.id) 
    915                         post server_name, RPL_WHOISBOT, @nick, nick, "is a \002Bot\002 on #{server_name}" 
    916                 end 
    917         end 
    918  
    919         def on_who(m) 
    920                 channel = m.params[0] 
    921                 case 
    922                 when channel.casecmp(main_channel).zero? 
    923                         users = [@me] 
    924                         users.concat @friends.reverse if @friends 
    925                         users.each {|friend| whoreply channel, friend } 
    926                         post server_name, RPL_ENDOFWHO, @nick, channel 
    927                 when (@groups.key?(channel) and @friends) 
    928                         @groups[channel].each do |nick| 
    929                                 whoreply channel, friend(nick) 
    930                         end 
    931                         post server_name, RPL_ENDOFWHO, @nick, channel 
    932                 else 
    933                         post server_name, ERR_NOSUCHNICK, @nick, "No such nick/channel" 
    934                 end 
    935         end 
    936  
    937         def on_join(m) 
    938                 channels = m.params[0].split(/ *, */) 
    939                 channels.each do |channel| 
    940                         channel = channel.split(" ", 2).first 
    941                         next if channel.casecmp(main_channel).zero? 
    942  
    943                         @channels << channel 
    944                         @channels.uniq! 
    945                         post @prefix, JOIN, channel 
    946                         post server_name, MODE, channel, "+mtio", @nick 
    947                         save_config 
    948                 end 
    949         end 
    950  
    951         def on_part(m) 
    952                 channel = m.params[0] 
    953                 return if channel.casecmp(main_channel).zero? 
    954  
    955                 @channels.delete(channel) 
    956                 post @prefix, PART, channel, "Ignore group #{channel}, but setting is alive yet." 
    957         end 
    958  
    959         def on_invite(m) 
    960                 nick, channel = *m.params 
    961                 if not nick.nick? or @nick.casecmp(nick).zero? 
    962                         post server_name, ERR_NOSUCHNICK, nick, "No such nick/channel" # or yourself 
    963                         return 
    964                 end 
    965  
    966                 friend = friend(nick) 
    967  
    968                 case 
    969                 when channel.casecmp(main_channel).zero? 
    970                         case 
    971                         when friend #TODO 
    972                         when api("users/username_available", { :username => nick }).valid 
    973                                 post server_name, ERR_NOSUCHNICK, nick, "No such nick/channel" 
    974                         else 
    975                                 user = api("friendships/create/#{nick}") 
    976                                 join main_channel, [user] 
    977                                 @friends << user if @friends 
    978                                 @me.friends_count += 1 
    979                         end 
    980                 when friend 
    981                         ((@groups[channel] ||= []) << friend.screen_name).uniq! 
    982                         join channel, [friend] 
    983                         save_config 
    984                 else 
    985                         post server_name, ERR_NOSUCHNICK, nick, "No such nick/channel" 
    986                 end 
    987         end 
    988  
    989         def on_kick(m) 
    990                 channel, nick, msg = *m.params 
    991  
    992                 if channel.casecmp(main_channel).zero? 
    993                         @friends.delete_if do |friend| 
    994                                 if friend.screen_name.casecmp(nick).zero? 
    995                                         user = api("friendships/destroy/#{friend.id}") 
    996                                         if user.is_a? User 
    997                                                 post prefix(user), PART, main_channel, "Removed: #{msg}" 
    998                                                 @me.friends_count -= 1 
    999                                         end 
    1000                                 end 
    1001                         end if @friends 
    1002                 else 
    1003                         friend = friend(nick) 
    1004                         if friend 
    1005                                 (@groups[channel] ||= []).delete(friend.screen_name) 
    1006                                 post prefix(friend), PART, channel, "Removed: #{msg}" 
    1007                                 save_config 
    1008                         else 
    1009                                 post server_name, ERR_NOSUCHNICK, nick, "No such nick/channel" 
    1010                         end 
    1011                 end 
    1012         end 
    1013  
    1014         #def on_nick(m) 
    1015         #       @nicknames[@nick] = m.params[0] 
    1016         #end 
    1017  
    1018         def on_topic(m) 
    1019                 channel = m.params[0] 
    1020                 return if not channel.casecmp(main_channel).zero? or @me.status.nil? 
    1021  
    1022                 begin 
    1023                         require "levenshtein" 
    1024                         topic    = m.params[1] 
    1025                         previous = @me.status 
    1026                         return unless previous 
    1027  
    1028                         distance = Levenshtein.normalized_distance(previous.text, topic) 
    1029                         return if distance.zero? 
    1030  
    1031                         status = api("statuses/update", { :status => topic, :source => source }) 
    1032                         log oops(ret) if status.truncated 
    1033                         status.user.status = status 
    1034                         @me = status.user 
    1035  
    1036                         if distance < 0.5 
    1037                                 deleted = api("statuses/destroy/#{previous.id}") 
    1038                                 @timeline.delete_if {|tid, s| s.id == deleted.id } 
    1039                                 log "Fixed: #{status.text}" 
    1040                         else 
    1041                                 log "Status updated" 
    1042                         end 
    1043                 rescue LoadError 
    1044                 end 
    1045         end 
    1046  
    1047         def on_mode(m) 
    1048                 channel = m.params[0] 
    1049  
    1050                 unless m.params[1] 
    1051                         if channel.ch? 
    1052                                 mode = "+mt" 
    1053                                 mode += "i" unless channel.casecmp(main_channel).zero? 
    1054                                 post server_name, RPL_CHANNELMODEIS, @nick, channel, mode 
    1055                                 #post server_name, RPL_CREATEONTIME, @nick, channel, 0 
    1056                         elsif channel.casecmp(@nick).zero? 
    1057                                 post server_name, RPL_UMODEIS, @nick, @nick, "+o" 
    1058                         end 
    1059                 end 
    1060         end 
    1061  
    1062         private 
     1066        end 
     1067 
    10631068        def on_ctcp_clientinfo(target, msg) 
    10641069                if user = user(target) 
     
    10921097                                user.time_zone, 
    10931098                        ]) 
    1094                 end 
    1095         end 
    1096  
    1097         def check_timeline 
    1098                 cmd = PRIVMSG 
    1099                 q   = { :count => 200 } 
    1100                 if @latest_id ||= nil 
    1101                         q.update(:since_id => @latest_id) 
    1102                 elsif not @me.statuses_count.zero? and not @me.friends_count.zero? 
    1103                         cmd = NOTICE 
    1104                 end 
    1105  
    1106                 api("statuses/friends_timeline", q).reverse_each do |status| 
    1107                         id = @latest_id = status.id 
    1108                         next if @timeline.any? {|tid, s| s.id == id } 
    1109  
    1110                         status.user.status = status 
    1111                         user = status.user 
    1112                         tid  = @timeline.push(status) 
    1113                         tid  = nil unless @opts.tid 
    1114  
    1115                         @log.debug [id, user.screen_name, status.text].inspect 
    1116  
    1117                         if user.id == @me.id 
    1118                                 mesg = generate_status_message(status.text) 
    1119                                 mesg << " " << @opts.tid % tid if tid 
    1120                                 post @prefix, TOPIC, main_channel, mesg 
    1121  
    1122                                 @me = user 
    1123                         else 
    1124                                 if @friends 
    1125                                         b = false 
    1126                                         @friends.each_with_index do |friend, i| 
    1127                                                 if b = friend.id == user.id 
    1128                                                         @friends[i] = user 
    1129                                                         break 
    1130                                                 end 
    1131                                         end 
    1132                                         unless b 
    1133                                                 join main_channel, [user] 
    1134                                                 @friends << user 
    1135                                                 @me.friends_count += 1 
    1136                                         end 
    1137                                 end 
    1138  
    1139                                 message(status, main_channel, tid, nil, cmd) 
    1140                         end 
    1141                         @groups.each do |channel, members| 
    1142                                 next unless members.include?(user.screen_name) 
    1143                                 message(status, channel, tid, nil, cmd) 
    1144                         end 
    1145                 end 
    1146         end 
    1147  
    1148         def generate_status_message(mesg) 
    1149                 mesg = decode_utf7(mesg) 
    1150                 mesg.delete!("\000\001") 
    1151                 mesg.gsub!("&gt;", ">") 
    1152                 mesg.gsub!("&lt;", "<") 
    1153                 #mesg.gsub!(/\r\n|[\r\n\t\u00A0\u1680\u180E\u2002-\u200D\u202F\u205F\u2060\uFEFF]/, " ") 
    1154                 mesg.gsub!(/\r\n|[\r\n\t]/, " ") 
    1155                 mesg = untinyurl(mesg) 
    1156                 mesg.sub!(@rsuffix_regex, "") if @rsuffix_regex 
    1157                 mesg.strip 
    1158         end 
    1159  
    1160         def prefix(u) 
    1161                 nick = u.screen_name 
    1162                 nick = "@#{nick}" if @opts.athack 
    1163                 user = "id=%.9d" % u.id 
    1164                 host = api_base.host 
    1165                 host += "/protected" if u.protected 
    1166                 host += "/bot"       if @drones.include?(u.id) 
    1167  
    1168                 Prefix.new("#{nick}!#{user}@#{host}") 
    1169         end 
    1170  
    1171         def friend(id) 
    1172                 return nil unless @friends 
    1173                 if id.is_a? String 
    1174                         @friends.find {|i| i.screen_name.casecmp(id).zero? } 
    1175                 else 
    1176                         @friends.find {|i| i.id == id } 
    1177                 end 
    1178         end 
    1179  
    1180         def user(id) 
    1181                 if id.is_a? String 
    1182                         @nick.casecmp(id).zero? ? @me : friend(id) 
    1183                 else 
    1184                         @me.id == id ? @me : friend(id) 
    1185                 end 
    1186         end 
    1187  
    1188         def check_mentions 
    1189                 return if @timeline.empty? 
    1190                 @prev_mention_id ||= @timeline.last.id 
    1191                 api("statuses/mentions", { 
    1192                         :count    => 200, 
    1193                         :since_id => @prev_mention_id 
    1194                 }).reverse_each do |mention| 
    1195                         id = @prev_mention_id = mention.id 
    1196                         next if @timeline.any? {|tid, s| s.id == id } 
    1197  
    1198                         mention.user.status = mention 
    1199                         user = mention.user 
    1200                         tid  = @timeline.push(mention) 
    1201                         tid  = nil unless @opts.tid 
    1202  
    1203                         @log.debug [id, user.screen_name, mention.text].inspect 
    1204                         message(mention, main_channel, tid) 
    1205  
    1206                         @friends.each_with_index do |friend, i| 
    1207                                 if friend.id == user.id 
    1208                                         @friends[i] = user 
    1209                                         break 
    1210                                 end 
    1211                         end if @friends 
    1212                 end 
    1213         end 
    1214  
    1215         def check_direct_messages 
    1216                 @prev_dm_id ||= nil 
    1217                 q = @prev_dm_id ? { :count => 200, :since_id => @prev_dm_id } \ 
    1218                                 : { :count => 1 } 
    1219                 api("direct_messages", q).reverse_each do |mesg| 
    1220                         unless @prev_dm_id &&= mesg.id 
    1221                                 @prev_dm_id = mesg.id 
    1222                                 next 
    1223                         end 
    1224  
    1225                         id   = mesg.id 
    1226                         user = mesg.sender 
    1227                         tid  = nil 
    1228                         text = mesg.text 
    1229                         @log.debug [id, user.screen_name, text].inspect 
    1230                         message(user, @nick, tid, text) 
    12311099                end 
    12321100        end 
     
    12771145        end 
    12781146 
    1279         def whoreply(channel, user) 
    1280                 #     "<channel> <user> <host> <server> <nick> 
    1281                 #         ( "H" / "G" > ["*"] [ ( "@" / "+" ) ] 
    1282                 #             :<hopcount> <real name>" 
    1283                 prefix = prefix(user) 
    1284                 server = api_base.host 
    1285                 real   = user.name 
    1286                 mode   = case prefix.nick 
    1287                         when @nick                     then "@" 
    1288                         #when @drones.include?(user.id) then "%" # FIXME 
    1289                         else                                "+" 
    1290                 end 
    1291                 post server_name, RPL_WHOREPLY, @nick, channel, 
    1292                      prefix.user, prefix.host, server, prefix.nick, "H*#{mode}", "0 #{real}" 
    1293         end 
    1294  
    1295         def join(channel, users) 
    1296                 max_params_count = @opts.max_params_count || 3 
    1297                 params = [] 
    1298                 users.each do |user| 
    1299                         prefix = prefix(user) 
    1300                         post prefix, JOIN, channel 
    1301                         params << prefix.nick 
    1302                         next if params.size < max_params_count 
    1303  
    1304                         post server_name, MODE, channel, "+#{"v" * params.size}", *params 
    1305                         params = [] 
    1306                 end 
    1307                 post server_name, MODE, channel, "+#{"v" * params.size}", *params unless params.empty? 
    1308                 users 
     1147        def check_timeline 
     1148                cmd = PRIVMSG 
     1149                q   = { :count => 200 } 
     1150                if @latest_id ||= nil 
     1151                        q.update(:since_id => @latest_id) 
     1152                elsif not @me.statuses_count.zero? and not @me.friends_count.zero? 
     1153                        cmd = NOTICE 
     1154                end 
     1155 
     1156                api("statuses/friends_timeline", q).reverse_each do |status| 
     1157                        id = @latest_id = status.id 
     1158                        next if @timeline.any? {|tid, s| s.id == id } 
     1159 
     1160                        status.user.status = status 
     1161                        user = status.user 
     1162                        tid  = @timeline.push(status) 
     1163                        tid  = nil unless @opts.tid 
     1164 
     1165                        @log.debug [id, user.screen_name, status.text].inspect 
     1166 
     1167                        if user.id == @me.id 
     1168                                mesg = generate_status_message(status.text) 
     1169                                mesg << " " << @opts.tid % tid if tid 
     1170                                post @prefix, TOPIC, main_channel, mesg 
     1171 
     1172                                @me = user 
     1173                        else 
     1174                                if @friends 
     1175                                        b = false 
     1176                                        @friends.each_with_index do |friend, i| 
     1177                                                if b = friend.id == user.id 
     1178                                                        @friends[i] = user 
     1179                                                        break 
     1180                                                end 
     1181                                        end 
     1182                                        unless b 
     1183                                                join main_channel, [user] 
     1184                                                @friends << user 
     1185                                                @me.friends_count += 1 
     1186                                        end 
     1187                                end 
     1188 
     1189                                message(status, main_channel, tid, nil, cmd) 
     1190                        end 
     1191                        @groups.each do |channel, members| 
     1192                                next unless members.include?(user.screen_name) 
     1193                                message(status, channel, tid, nil, cmd) 
     1194                        end 
     1195                end 
     1196        end 
     1197 
     1198        def check_direct_messages 
     1199                @prev_dm_id ||= nil 
     1200                q = @prev_dm_id ? { :count => 200, :since_id => @prev_dm_id } \ 
     1201                                : { :count => 1 } 
     1202                api("direct_messages", q).reverse_each do |mesg| 
     1203                        unless @prev_dm_id &&= mesg.id 
     1204                                @prev_dm_id = mesg.id 
     1205                                next 
     1206                        end 
     1207 
     1208                        id   = mesg.id 
     1209                        user = mesg.sender 
     1210                        tid  = nil 
     1211                        text = mesg.text 
     1212                        @log.debug [id, user.screen_name, text].inspect 
     1213                        message(user, @nick, tid, text) 
     1214                end 
     1215        end 
     1216 
     1217        def check_mentions 
     1218                return if @timeline.empty? 
     1219                @prev_mention_id ||= @timeline.last.id 
     1220                api("statuses/mentions", { 
     1221                        :count    => 200, 
     1222                        :since_id => @prev_mention_id 
     1223                }).reverse_each do |mention| 
     1224                        id = @prev_mention_id = mention.id 
     1225                        next if @timeline.any? {|tid, s| s.id == id } 
     1226 
     1227                        mention.user.status = mention 
     1228                        user = mention.user 
     1229                        tid  = @timeline.push(mention) 
     1230                        tid  = nil unless @opts.tid 
     1231 
     1232                        @log.debug [id, user.screen_name, mention.text].inspect 
     1233                        message(mention, main_channel, tid) 
     1234 
     1235                        @friends.each_with_index do |friend, i| 
     1236                                if friend.id == user.id 
     1237                                        @friends[i] = user 
     1238                                        break 
     1239                                end 
     1240                        end if @friends 
     1241                end 
    13091242        end 
    13101243 
     
    13391272                @log.error e.inspect 
    13401273                100 
     1274        end 
     1275 
     1276        def join(channel, users) 
     1277                max_params_count = @opts.max_params_count || 3 
     1278                params = [] 
     1279                users.each do |user| 
     1280                        prefix = prefix(user) 
     1281                        post prefix, JOIN, channel 
     1282                        params << prefix.nick 
     1283                        next if params.size < max_params_count 
     1284 
     1285                        post server_name, MODE, channel, "+#{"v" * params.size}", *params 
     1286                        params = [] 
     1287                end 
     1288                post server_name, MODE, channel, "+#{"v" * params.size}", *params unless params.empty? 
     1289                users 
    13411290        end 
    13421291 
     
    13781327        end 
    13791328 
     1329        def whoreply(channel, user) 
     1330                #     "<channel> <user> <host> <server> <nick> 
     1331                #         ( "H" / "G" > ["*"] [ ( "@" / "+" ) ] 
     1332                #             :<hopcount> <real name>" 
     1333                prefix = prefix(user) 
     1334                server = api_base.host 
     1335                real   = user.name 
     1336                mode   = case prefix.nick 
     1337                        when @nick                     then "@" 
     1338                        #when @drones.include?(user.id) then "%" # FIXME 
     1339                        else                                "+" 
     1340                end 
     1341                post server_name, RPL_WHOREPLY, @nick, channel, 
     1342                     prefix.user, prefix.host, server, prefix.nick, "H*#{mode}", "0 #{real}" 
     1343        end 
     1344 
    13801345        def save_config 
    13811346                config = { 
     
    15131478        end 
    15141479 
     1480        def generate_status_message(mesg) 
     1481                mesg = decode_utf7(mesg) 
     1482                mesg.delete!("\000\001") 
     1483                mesg.gsub!("&gt;", ">") 
     1484                mesg.gsub!("&lt;", "<") 
     1485                #mesg.gsub!(/\r\n|[\r\n\t\u00A0\u1680\u180E\u2002-\u200D\u202F\u205F\u2060\uFEFF]/, " ") 
     1486                mesg.gsub!(/\r\n|[\r\n\t]/, " ") 
     1487                mesg = untinyurl(mesg) 
     1488                mesg.sub!(@rsuffix_regex, "") if @rsuffix_regex 
     1489                mesg.strip 
     1490        end 
     1491 
     1492        def friend(id) 
     1493                return nil unless @friends 
     1494                if id.is_a? String 
     1495                        @friends.find {|i| i.screen_name.casecmp(id).zero? } 
     1496                else 
     1497                        @friends.find {|i| i.id == id } 
     1498                end 
     1499        end 
     1500 
     1501        def user(id) 
     1502                if id.is_a? String 
     1503                        @nick.casecmp(id).zero? ? @me : friend(id) 
     1504                else 
     1505                        @me.id == id ? @me : friend(id) 
     1506                end 
     1507        end 
     1508 
     1509        def prefix(u) 
     1510                nick = u.screen_name 
     1511                nick = "@#{nick}" if @opts.athack 
     1512                user = "id=%.9d" % u.id 
     1513                host = api_base.host 
     1514                host += "/protected" if u.protected 
     1515                host += "/bot"       if @drones.include?(u.id) 
     1516 
     1517                Prefix.new("#{nick}!#{user}@#{host}") 
     1518        end 
     1519 
    15151520        def message(struct, target, tid = nil, str = nil, command = PRIVMSG) 
    15161521                unless str 
     
    15351540        def log(str) 
    15361541                post server_name, NOTICE, main_channel, str.gsub(/\r\n|[\r\n]/, " ") 
     1542        end 
     1543 
     1544        def decode_utf7(str) 
     1545                return str unless defined?(::Iconv) and str.include?("+") 
     1546 
     1547                str.sub!(/\A(?:.+ > |.+\z)/) { Iconv.iconv("UTF-8", "UTF-7", $&).join } 
     1548                #FIXME str = "[utf7]: #{str}" if str =~ /[^a-z0-9\s]/i 
     1549                str 
     1550        rescue Iconv::IllegalSequence 
     1551                str 
     1552        rescue => e 
     1553                @log.error e 
     1554                str 
    15371555        end 
    15381556 
     
    16181636                @log.error e 
    16191637                text 
    1620         end 
    1621  
    1622         def resolve_http_redirect(uri, limit = 3) 
    1623                 return uri if limit.zero? or uri.nil? 
    1624                 @log.debug uri.inspect 
    1625  
    1626                 req = http_req :head, uri 
    1627                 http(uri, 3, 2).request(req) do |res| 
    1628                         break if not res.is_a?(Net::HTTPRedirection) or 
    1629                                  not res.key?("Location") 
    1630                         begin 
    1631                                 location = URI(res["Location"]) 
    1632                         rescue URI::InvalidURIError 
    1633                         end 
    1634                         unless location.is_a? URI::HTTP 
    1635                                 begin 
    1636                                         location = URI.join(uri.to_s, res["Location"]) 
    1637                                 rescue URI::InvalidURIError, URI::BadURIError 
    1638                                         # FIXME 
    1639                                 end 
    1640                         end 
    1641                         uri = resolve_http_redirect(location, limit - 1) 
    1642                 end 
    1643  
    1644                 uri 
    1645         rescue => e 
    1646                 @log.error e.inspect 
    1647                 uri 
    1648         end 
    1649  
    1650         def decode_utf7(str) 
    1651                 return str unless defined?(::Iconv) and str.include?("+") 
    1652  
    1653                 str.sub!(/\A(?:.+ > |.+\z)/) { Iconv.iconv("UTF-8", "UTF-7", $&).join } 
    1654                 #FIXME str = "[utf7]: #{str}" if str =~ /[^a-z0-9\s]/i 
    1655                 str 
    1656         rescue Iconv::IllegalSequence 
    1657                 str 
    1658         rescue => e 
    1659                 @log.error e 
    1660                 str 
    1661         end 
    1662  
    1663         def fetch_sources(n = nil) 
    1664                 n    = n.to_i 
    1665                 uri  = URI("http://wedata.net/databases/TwitterSources/items.json") 
    1666                 @log.debug uri.inspect 
    1667                 json = http(uri).request(http_req(:get, uri)).body 
    1668                 sources = JSON.parse json 
    1669                 sources.map! {|item| [item["data"]["source"], item["name"]] }.push ["", "web"] 
    1670                 if (1 ... sources.size).include?(n) 
    1671                         sources = Array.new(n) { sources.delete_at(rand(sources.size)) }.compact 
    1672                 end 
    1673                 sources 
    1674         rescue => e 
    1675                 @log.error e.inspect 
    1676                 log "An error occured while loading #{uri.host}." 
    1677                 @sources || [[api_source, "tig.rb"]] 
    1678         end 
    1679  
    1680         def update_redundant_suffix 
    1681                 uri = URI("http://svn.coderepos.org/share/platform/twitterircgateway/suffixesblacklist.txt") 
    1682                 @log.debug uri.inspect 
    1683                 res = http(uri).request(http_req(:get, uri)) 
    1684                 @etags[uri.to_s] = res["ETag"] 
    1685                 return if res.is_a? Net::HTTPNotModified 
    1686                 source = res.body 
    1687                 source.encoding!("UTF-8") if source.respond_to?(:encoding) and source.encoding == Encoding::BINARY 
    1688                 @rsuffix_regex = /#{Regexp.union(*source.split)}\z/ 
    1689         rescue Errno::ECONNREFUSED, Timeout::Error => e 
    1690                 @log.error "Failed to get the redundant suffix blacklist from #{uri.host}: #{e.inspect}" 
    1691         end 
    1692  
    1693         def http_req(method, uri, header = {}, credentials = nil) 
    1694                 accepts = ["*/*;q=0.1"] 
    1695                 #require "mime/types"; accepts.unshift MIME::Types.of(uri.path).first.simplified 
    1696                 types   = { "json" => "application/json", "txt" => "text/plain" } 
    1697                 ext     = uri.path[/[^.]+\z/] 
    1698                 accepts.unshift types[ext] if types.key?(ext) 
    1699                 user_agent = "#{self.class}/#{server_version} (#{File.basename(__FILE__)}; net-irc) Ruby/#{RUBY_VERSION} (#{RUBY_PLATFORM})" 
    1700  
    1701                 header["User-Agent"]      ||= user_agent 
    1702                 header["Accept"]          ||= accepts.join(",") 
    1703                 header["Accept-Charset"]  ||= "UTF-8,*;q=0.0" if ext != "json" 
    1704                 #header["Accept-Language"] ||= @opts.lang # "en-us,en;q=0.9,ja;q=0.5" 
    1705                 header["If-None-Match"]   ||= @etags[uri.to_s] if @etags[uri.to_s] 
    1706  
    1707                 req = case method.to_s.downcase.to_sym 
    1708                 when :get 
    1709                         Net::HTTP::Get.new    uri.request_uri, header 
    1710                 when :head 
    1711                         Net::HTTP::Head.new   uri.request_uri, header 
    1712                 when :post 
    1713                         Net::HTTP::Post.new   uri.path,        header 
    1714                 when :put 
    1715                         Net::HTTP::Put.new    uri.path,        header 
    1716                 when :delete 
    1717                         Net::HTTP::Delete.new uri.request_uri, header 
    1718                 else # raise "" 
    1719                 end 
    1720                 if req.request_body_permitted? 
    1721                         req["Content-Type"] ||= "application/x-www-form-urlencoded" 
    1722                         req.body = uri.query 
    1723                 end 
    1724                 req.basic_auth(*credentials) if credentials 
    1725                 req 
    1726         rescue => e 
    1727                 @log.error e 
    1728         end 
    1729  
    1730         def http(uri, open_timeout = nil, read_timeout = 60) 
    1731                 http = case 
    1732                         when @httpproxy 
    1733                                 Net::HTTP.new(uri.host, uri.port, @httpproxy.address, @httpproxy.port, 
    1734                                                                   @httpproxy.user, @httpproxy.password) 
    1735                         when ENV["HTTP_PROXY"], ENV["http_proxy"] 
    1736                                 proxy = URI(ENV["HTTP_PROXY"] || ENV["http_proxy"]) 
    1737                                 Net::HTTP.new(uri.host, uri.port, proxy.host, proxy.port, 
    1738                                                                   proxy.user, proxy.password) 
    1739                         else 
    1740                                 Net::HTTP.new(uri.host, uri.port) 
    1741                 end 
    1742                 http.open_timeout = open_timeout if open_timeout # nil by default 
    1743                 http.read_timeout = read_timeout if read_timeout # 60 by default 
    1744                 if uri.is_a? URI::HTTPS 
    1745                         http.use_ssl     = true 
    1746                         http.verify_mode = OpenSSL::SSL::VERIFY_NONE 
    1747                 end 
    1748                 http 
    1749         rescue => e 
    1750                 @log.error e 
    1751         end 
    1752  
    1753         def exist_uri?(uri, limit = 1) 
    1754                 ret = nil 
    1755                 #raise "Not supported." unless uri.is_a?(URI::HTTP) 
    1756                 return ret if limit.zero? or uri.nil? or not uri.is_a?(URI::HTTP) 
    1757                 @log.debug uri.inspect 
    1758  
    1759                 req = http_req :head, uri 
    1760                 http(uri, 3, 2).request(req) do |res| 
    1761                         ret = case res 
    1762                                 when Net::HTTPSuccess 
    1763                                         true 
    1764                                 when Net::HTTPRedirection 
    1765                                         uri = resolve_http_redirect(uri) 
    1766                                         exist_uri?(uri, limit - 1) 
    1767                                 when Net::HTTPClientError 
    1768                                         false 
    1769                                 #when Net::HTTPServerError 
    1770                                 #       nil 
    1771                                 else 
    1772                                         nil 
    1773                         end 
    1774                 end 
    1775  
    1776                 ret 
    1777         rescue => e 
    1778                 @log.error e.inspect 
    1779                 ret 
    17801638        end 
    17811639 
     
    18271685                @log.error e 
    18281686                text 
     1687        end 
     1688 
     1689        def exist_uri?(uri, limit = 1) 
     1690                ret = nil 
     1691                #raise "Not supported." unless uri.is_a?(URI::HTTP) 
     1692                return ret if limit.zero? or uri.nil? or not uri.is_a?(URI::HTTP) 
     1693                @log.debug uri.inspect 
     1694 
     1695                req = http_req :head, uri 
     1696                http(uri, 3, 2).request(req) do |res| 
     1697                        ret = case res 
     1698                                when Net::HTTPSuccess 
     1699                                        true 
     1700                                when Net::HTTPRedirection 
     1701                                        uri = resolve_http_redirect(uri) 
     1702                                        exist_uri?(uri, limit - 1) 
     1703                                when Net::HTTPClientError 
     1704                                        false 
     1705                                #when Net::HTTPServerError 
     1706                                #       nil 
     1707                                else 
     1708                                        nil 
     1709                        end 
     1710                end 
     1711 
     1712                ret 
     1713        rescue => e 
     1714                @log.error e.inspect 
     1715                ret 
     1716        end 
     1717 
     1718        def resolve_http_redirect(uri, limit = 3) 
     1719                return uri if limit.zero? or uri.nil? 
     1720                @log.debug uri.inspect 
     1721 
     1722                req = http_req :head, uri 
     1723                http(uri, 3, 2).request(req) do |res| 
     1724                        break if not res.is_a?(Net::HTTPRedirection) or 
     1725                                 not res.key?("Location") 
     1726                        begin 
     1727                                location = URI(res["Location"]) 
     1728                        rescue URI::InvalidURIError 
     1729                        end 
     1730                        unless location.is_a? URI::HTTP 
     1731                                begin 
     1732                                        location = URI.join(uri.to_s, res["Location"]) 
     1733                                rescue URI::InvalidURIError, URI::BadURIError 
     1734                                        # FIXME 
     1735                                end 
     1736                        end 
     1737                        uri = resolve_http_redirect(location, limit - 1) 
     1738                end 
     1739 
     1740                uri 
     1741        rescue => e 
     1742                @log.error e.inspect 
     1743                uri 
     1744        end 
     1745 
     1746        def fetch_sources(n = nil) 
     1747                n    = n.to_i 
     1748                uri  = URI("http://wedata.net/databases/TwitterSources/items.json") 
     1749                @log.debug uri.inspect 
     1750                json = http(uri).request(http_req(:get, uri)).body 
     1751                sources = JSON.parse json 
     1752                sources.map! {|item| [item["data"]["source"], item["name"]] }.push ["", "web"] 
     1753                if (1 ... sources.size).include?(n) 
     1754                        sources = Array.new(n) { sources.delete_at(rand(sources.size)) }.compact 
     1755                end 
     1756                sources 
     1757        rescue => e 
     1758                @log.error e.inspect 
     1759                log "An error occured while loading #{uri.host}." 
     1760                @sources || [[api_source, "tig.rb"]] 
     1761        end 
     1762 
     1763        def update_redundant_suffix 
     1764                uri = URI("http://svn.coderepos.org/share/platform/twitterircgateway/suffixesblacklist.txt") 
     1765                @log.debug uri.inspect 
     1766                res = http(uri).request(http_req(:get, uri)) 
     1767                @etags[uri.to_s] = res["ETag"] 
     1768                return if res.is_a? Net::HTTPNotModified 
     1769                source = res.body 
     1770                source.encoding!("UTF-8") if source.respond_to?(:encoding) and source.encoding == Encoding::BINARY 
     1771                @rsuffix_regex = /#{Regexp.union(*source.split)}\z/ 
     1772        rescue Errno::ECONNREFUSED, Timeout::Error => e 
     1773                @log.error "Failed to get the redundant suffix blacklist from #{uri.host}: #{e.inspect}" 
     1774        end 
     1775 
     1776        def http(uri, open_timeout = nil, read_timeout = 60) 
     1777                http = case 
     1778                        when @httpproxy 
     1779                                Net::HTTP.new(uri.host, uri.port, @httpproxy.address, @httpproxy.port, 
     1780                                                                  @httpproxy.user, @httpproxy.password) 
     1781                        when ENV["HTTP_PROXY"], ENV["http_proxy"] 
     1782                                proxy = URI(ENV["HTTP_PROXY"] || ENV["http_proxy"]) 
     1783                                Net::HTTP.new(uri.host, uri.port, proxy.host, proxy.port, 
     1784                                                                  proxy.user, proxy.password) 
     1785                        else 
     1786                                Net::HTTP.new(uri.host, uri.port) 
     1787                end 
     1788                http.open_timeout = open_timeout if open_timeout # nil by default 
     1789                http.read_timeout = read_timeout if read_timeout # 60 by default 
     1790                if uri.is_a? URI::HTTPS 
     1791                        http.use_ssl     = true 
     1792                        http.verify_mode = OpenSSL::SSL::VERIFY_NONE 
     1793                end 
     1794                http 
     1795        rescue => e 
     1796                @log.error e 
     1797        end 
     1798 
     1799        def http_req(method, uri, header = {}, credentials = nil) 
     1800                accepts = ["*/*;q=0.1"] 
     1801                #require "mime/types"; accepts.unshift MIME::Types.of(uri.path).first.simplified 
     1802                types   = { "json" => "application/json", "txt" => "text/plain" } 
     1803                ext     = uri.path[/[^.]+\z/] 
     1804                accepts.unshift types[ext] if types.key?(ext) 
     1805                user_agent = "#{self.class}/#{server_version} (#{File.basename(__FILE__)}; net-irc) Ruby/#{RUBY_VERSION} (#{RUBY_PLATFORM})" 
     1806 
     1807                header["User-Agent"]      ||= user_agent 
     1808                header["Accept"]          ||= accepts.join(",") 
     1809                header["Accept-Charset"]  ||= "UTF-8,*;q=0.0" if ext != "json" 
     1810                #header["Accept-Language"] ||= @opts.lang # "en-us,en;q=0.9,ja;q=0.5" 
     1811                header["If-None-Match"]   ||= @etags[uri.to_s] if @etags[uri.to_s] 
     1812 
     1813                req = case method.to_s.downcase.to_sym 
     1814                when :get 
     1815                        Net::HTTP::Get.new    uri.request_uri, header 
     1816                when :head 
     1817                        Net::HTTP::Head.new   uri.request_uri, header 
     1818                when :post 
     1819                        Net::HTTP::Post.new   uri.path,        header 
     1820                when :put 
     1821                        Net::HTTP::Put.new    uri.path,        header 
     1822                when :delete 
     1823                        Net::HTTP::Delete.new uri.request_uri, header 
     1824                else # raise "" 
     1825                end 
     1826                if req.request_body_permitted? 
     1827                        req["Content-Type"] ||= "application/x-www-form-urlencoded" 
     1828                        req.body = uri.query 
     1829                end 
     1830                req.basic_auth(*credentials) if credentials 
     1831                req 
     1832        rescue => e 
     1833                @log.error e 
    18291834        end 
    18301835