| | 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 |
| 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 | |
| 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!(">", ">") |
| 1152 | | mesg.gsub!("<", "<") |
| 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) |
| 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 |
| 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 |
| | 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 |