| 1 | |
|---|
| 2 | require 'chokan/plugin_base' |
|---|
| 3 | require 'net/https' |
|---|
| 4 | require 'net/ftp' |
|---|
| 5 | require "cgi" |
|---|
| 6 | require "image_size" |
|---|
| 7 | require "digest/md5" |
|---|
| 8 | require "timeout" |
|---|
| 9 | |
|---|
| 10 | class UriInformation < Chokan::PluginBase |
|---|
| 11 | MAX_REDIRECT = 10 |
|---|
| 12 | HEADER = { |
|---|
| 13 | "User-Agent" => "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.5) Gecko/20041107 Firefox/1.0", |
|---|
| 14 | "Accept" => "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5", |
|---|
| 15 | } |
|---|
| 16 | |
|---|
| 17 | def initialize(config, chokan) |
|---|
| 18 | super |
|---|
| 19 | @uri = /(#{URI.regexp(@config['accept_scheme']).to_s})/ |
|---|
| 20 | end |
|---|
| 21 | |
|---|
| 22 | def on_privmsg(prefix, channel, message) |
|---|
| 23 | @channel = channel |
|---|
| 24 | message.scan(@uri) do |uri| |
|---|
| 25 | begin |
|---|
| 26 | uri = uri[0] |
|---|
| 27 | uri = URI(uri) |
|---|
| 28 | mes = nil |
|---|
| 29 | timeout(5) do |
|---|
| 30 | mes = chaining_try(uri) |
|---|
| 31 | end |
|---|
| 32 | log mes |
|---|
| 33 | notice(channel, mes) if mes |
|---|
| 34 | rescue Exception => e |
|---|
| 35 | notice(channel, e.inspect) |
|---|
| 36 | end |
|---|
| 37 | end |
|---|
| 38 | end |
|---|
| 39 | |
|---|
| 40 | |
|---|
| 41 | def chaining_try(uri) |
|---|
| 42 | ret = nil |
|---|
| 43 | @config['modules'].each do |mod| |
|---|
| 44 | begin |
|---|
| 45 | if mod.kind_of? Hash |
|---|
| 46 | name = mod.keys[0] |
|---|
| 47 | config = mod[name] |
|---|
| 48 | else |
|---|
| 49 | name = mod |
|---|
| 50 | config = {} |
|---|
| 51 | end |
|---|
| 52 | ret = send("handler_#{name}", config, uri) |
|---|
| 53 | break if ret |
|---|
| 54 | rescue Exception => e |
|---|
| 55 | log e |
|---|
| 56 | log name |
|---|
| 57 | log e.message |
|---|
| 58 | log *e.backtrace |
|---|
| 59 | end |
|---|
| 60 | end |
|---|
| 61 | ret |
|---|
| 62 | end |
|---|
| 63 | |
|---|
| 64 | |
|---|
| 65 | def handler_gigazine(config, uri) |
|---|
| 66 | return unless uri.scheme =~ /^https?/ |
|---|
| 67 | require "bdb" |
|---|
| 68 | c = config.find {|h| h["channel"] == @channel } |
|---|
| 69 | log c.inspect |
|---|
| 70 | return unless c |
|---|
| 71 | |
|---|
| 72 | db = BDB::Hash.open(c["db"], nil, 0, 0644) |
|---|
| 73 | db.each do |k,v| |
|---|
| 74 | _, url, datetime, = */^(.+?):(\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d)$/.match(k) |
|---|
| 75 | if url == uri.to_s |
|---|
| 76 | notice(@channel, "GIGAZINE にものってるわ") |
|---|
| 77 | break |
|---|
| 78 | end |
|---|
| 79 | end |
|---|
| 80 | db.close |
|---|
| 81 | nil # continue chain |
|---|
| 82 | end |
|---|
| 83 | |
|---|
| 84 | |
|---|
| 85 | def handler_mixi(config, uri) |
|---|
| 86 | return unless uri.host == 'mixi.jp' |
|---|
| 87 | cookie = {} |
|---|
| 88 | login_uri = URI("http://mixi.jp/login.pl") |
|---|
| 89 | Net::HTTP.start(login_uri.host, login_uri.port) do |http| |
|---|
| 90 | data = { |
|---|
| 91 | "next_url" => "/home.pl", |
|---|
| 92 | "email" => config['user'], |
|---|
| 93 | "password" => config['pass'], |
|---|
| 94 | "sticky" => "" |
|---|
| 95 | } |
|---|
| 96 | data = data.collect {|k,v| "#{k}=#{URI.escape(v, /[^-.!~*'()\w]/n)}" }.join("&") |
|---|
| 97 | res = http.post(login_uri.request_uri, data, HEADER) |
|---|
| 98 | if res.key?("set-cookie") |
|---|
| 99 | res["set-cookie"].split(/\s*,\s*/).each do |c| |
|---|
| 100 | k, v =c.sub(/;.*/, "").split(/=/) |
|---|
| 101 | cookie[k] = v |
|---|
| 102 | end |
|---|
| 103 | #http.get("/check.pl?n=/home.pl", {"User-Agent" => UA} ) |
|---|
| 104 | else |
|---|
| 105 | raise LoginError, "Invalid e-mail or password" |
|---|
| 106 | end |
|---|
| 107 | end |
|---|
| 108 | |
|---|
| 109 | http(uri, { |
|---|
| 110 | "Cookie" => cookie.collect {|c| c.join("=") }.join(";") |
|---|
| 111 | }) |
|---|
| 112 | end |
|---|
| 113 | |
|---|
| 114 | def handler_http(config, uri) |
|---|
| 115 | return unless uri.scheme =~ /^https?/ |
|---|
| 116 | http(uri) |
|---|
| 117 | end |
|---|
| 118 | |
|---|
| 119 | def handler_urn(config, uri) |
|---|
| 120 | return unless uri.scheme == 'urn' |
|---|
| 121 | nid, *nss = uri.opaque.split(/:/) |
|---|
| 122 | case nid |
|---|
| 123 | when 'ietf' |
|---|
| 124 | subclass, *rest = nss |
|---|
| 125 | case subclass |
|---|
| 126 | when "rfc" |
|---|
| 127 | "http://www.ietf.org/rfc/rfc#{rest[0]}.txt" |
|---|
| 128 | end |
|---|
| 129 | when 'isbn' |
|---|
| 130 | "http://www.amazon.co.jp/gp/product/#{nss[0]}/" |
|---|
| 131 | end |
|---|
| 132 | end |
|---|
| 133 | |
|---|
| 134 | def handler_ftp(config, uri) |
|---|
| 135 | return unless uri.scheme == 'ftp' |
|---|
| 136 | ftp = Net::FTP.open(uri.host, uri.user || "anonymous", uri.password || "foobar@example.com") |
|---|
| 137 | begin |
|---|
| 138 | size = ftp.size(uri.path) |
|---|
| 139 | time = ftp.mtime(uri.path) |
|---|
| 140 | "[size: #{size} mtime:#{time}]" |
|---|
| 141 | rescue Net::FTPPermError => e |
|---|
| 142 | e.message |
|---|
| 143 | ensure |
|---|
| 144 | ftp.close |
|---|
| 145 | end |
|---|
| 146 | end |
|---|
| 147 | |
|---|
| 148 | def http(uri, header=HEADER, limit=MAX_REDIRECT) |
|---|
| 149 | return "Redirect loop?: last:#{uri}" if limit <= 0 |
|---|
| 150 | |
|---|
| 151 | log uri |
|---|
| 152 | ret = '' |
|---|
| 153 | http = Net::HTTP.new(uri.host, uri.port) |
|---|
| 154 | http.use_ssl = (uri.scheme == "https") |
|---|
| 155 | http.start do |http| |
|---|
| 156 | r = http.head(uri.request_uri, header) |
|---|
| 157 | log r.code.inspect |
|---|
| 158 | case r.code |
|---|
| 159 | when '200' |
|---|
| 160 | case r["Content-Type"] |
|---|
| 161 | when /html/ |
|---|
| 162 | ret = html(http.get(uri.request_uri, header.merge({ |
|---|
| 163 | "Range" => "0-5000" |
|---|
| 164 | }))) |
|---|
| 165 | when /image/ |
|---|
| 166 | ret = image(http.get(uri.request_uri, header)) |
|---|
| 167 | else |
|---|
| 168 | if r["Content-Length"] |
|---|
| 169 | size = r["Content-Length"].to_i / 1024 |
|---|
| 170 | ret = "[#{r["Content-Type"]}] #{size}KB" |
|---|
| 171 | else |
|---|
| 172 | ret = "[#{r["Content-Type"]}]" |
|---|
| 173 | end |
|---|
| 174 | end |
|---|
| 175 | when '401' |
|---|
| 176 | realm = r["WWW-Authenticate"][/Basic realm="([^"]+)"/, 1] |
|---|
| 177 | auth = @config["http_auth"].find {|e| e["host"] == uri.host and e["realm"] == realm } |
|---|
| 178 | if auth |
|---|
| 179 | auth = "Basic " + ["#{auth["user"]}:#{auth["pass"]}"].pack("m") |
|---|
| 180 | ret = http(uri, header.update({'Authorization' => auth}), limit-1) |
|---|
| 181 | else |
|---|
| 182 | ret = realm |
|---|
| 183 | end |
|---|
| 184 | when /^3/ |
|---|
| 185 | loc = URI(r["Location"]) |
|---|
| 186 | loc = uri + loc if loc.relative? |
|---|
| 187 | ret = http(loc, header, limit-1) |
|---|
| 188 | else |
|---|
| 189 | ret = "[#{r.code} #{r.message}]" |
|---|
| 190 | end |
|---|
| 191 | end |
|---|
| 192 | |
|---|
| 193 | rescue Exception => e |
|---|
| 194 | log e.message |
|---|
| 195 | log *e.backtrace |
|---|
| 196 | e.inspect |
|---|
| 197 | end |
|---|
| 198 | |
|---|
| 199 | def image(res) |
|---|
| 200 | size = res.body.length / 1024 |
|---|
| 201 | img = ImageSize.new(res.body) |
|---|
| 202 | ret = "#{img.get_type} Image, " |
|---|
| 203 | ret << "#{img.get_width || "?"}x#{img.get_height || "?"} " |
|---|
| 204 | ret << "#{size.to_i}KB" |
|---|
| 205 | end |
|---|
| 206 | |
|---|
| 207 | def html(res) |
|---|
| 208 | title = res.body[/<title.*?>(.*?)<\/title\s*>/imn, 1] |
|---|
| 209 | title = "タイトル無し " if !title || title.empty? |
|---|
| 210 | title = title.gsub(/\s+/, " ").gsub(/<.*?>/, "").to_u8 |
|---|
| 211 | title.gsub!(/&#(x)?([0-9a-f]+);/i) do |m| |
|---|
| 212 | [$1 ? $2.hex : $2.to_i].pack("U") |
|---|
| 213 | end |
|---|
| 214 | if title.size > 70 |
|---|
| 215 | title = title[/.{0,60}/] + "..." |
|---|
| 216 | end |
|---|
| 217 | |
|---|
| 218 | title = title.gsub(/>/, ">").gsub(/</, "<").gsub(/&/, "&") |
|---|
| 219 | |
|---|
| 220 | "#{title} [#{res["Content-Type"]}]" |
|---|
| 221 | end |
|---|
| 222 | end |
|---|
| 223 | |
|---|
| 224 | |
|---|