+ "url info <url> => display link info for <url> (set url.display_link_info > 0 if you want the bot to do it automatically when someone writes an url), urls [<max>=4] => list <max> last urls mentioned in current channel, urls search [<max>=4] <regexp> => search for matching urls. In a private message, you must specify the channel to query, eg. urls <channel> [max], urls search <channel> [max] <regexp>"
+ end
+
+ def get_title_from_html(pagedata)
+ return pagedata.ircify_html_title
+ end
+
+ def get_title_for_url(uri_str, opts = {})
+
+ url = uri_str.kind_of?(URI) ? uri_str : URI.parse(uri_str)
+ return if url.scheme !~ /https?/
+
+ # also check the ip, the canonical name and the aliases
+ begin
+ checks = TCPSocket.gethostbyname(url.host)
+ checks.delete_at(-2)
+ rescue => e
+ return "Unable to retrieve info for #{url.host}: #{e.message}"
+ end
+
+ checks << url.host
+ checks.flatten!
+
+ unless checks.grep(@no_info_hosts).empty?
+ return ( opts[:always_reply] ? "Sorry, info retrieval for #{url.host} (#{checks.first}) is disabled" : false )
+ end
+
+ logopts = opts.dup
+
+ title = nil
+ extra = []
+
+ begin
+ debug "+ getting info for #{url.request_uri}"
+ info = @bot.filter(:htmlinfo, url)
+ logopts[:htmlinfo] = info
+ resp = info[:headers]
+
+ logopts[:title] = title = info[:title]
+
+ if info[:content]
+ logopts[:extra] = info[:content]
+
+ max_length = @bot.config['url.first_par_length']
+
+ whitelist = @bot.config['url.first_par_whitelist']
+ content = nil
+ if whitelist.length > 0
+ whitelist.each do |pattern|
+ if Regexp.new(pattern, Regexp::IGNORECASE).match(url.to_s)
+ content = info[:content][0...max_length]
+ break
+ end
+ end
+ else
+ content = info[:content][0...max_length]
+ end
+
+ extra << "#{Bold}text#{Bold}: #{content}" if @bot.config['url.first_par'] and content
+ else
+ logopts[:extra] = String.new
+ logopts[:extra] << "Content Type: #{resp['content-type']}"
+ extra << "#{Bold}type#{Bold}: #{resp['content-type']}" unless title
+ if enc = resp['content-encoding']
+ logopts[:extra] << ", encoding: #{enc}"
+ extra << "#{Bold}encoding#{Bold}: #{enc}" if @bot.config['url.first_par'] or not title
+ end
+
+ size = resp['content-length'].first.gsub(/(\d)(?=\d{3}+(?:\.|$))(\d{3}\..*)?/,'\1,\2') rescue nil
+ if size
+ logopts[:extra] << ", size: #{size} bytes"
+ extra << "#{Bold}size#{Bold}: #{size} bytes" if @bot.config['url.first_par'] or not title
+ end
+ end
+ rescue Exception => e
+ case e
+ when UrlLinkError
+ raise e
+ else
+ error e
+ raise "connecting to site/processing information (#{e.message})"
+ end
+ end
+
+ call_event(:url_added, url.to_s, logopts)
+ if title
+ extra.unshift("#{Bold}title#{Bold}: #{title}")
+ end
+ return extra.join(", ") if title or not @bot.config['url.titles_only']
+ end
+
+ def handle_urls(m, params={})
+ opts = {
+ :display_info => @bot.config['url.display_link_info'],
+ :channels => @bot.config['url.only_on_channels'],
+ :ignore => @bot.config['url.ignore']
+ }.merge params
+ urls = opts[:urls]
+ display_info= opts[:display_info]
+ channels = opts[:channels]
+ ignore = opts[:ignore]
+
+ unless channels.empty?
+ return unless channels.map { |c| c.downcase }.include?(m.channel.downcase)
+ end
+
+ ignore.each { |u| return if m.source.matches?(u) }
+
+ return if urls.empty?
+ debug "found urls #{urls.inspect}"
+ list = m.public? ? @registry[m.target] : nil
+ debug "display link info: #{display_info}"
+ urls_displayed = 0
+ urls.each do |urlstr|
+ debug "working on #{urlstr}"
+ next unless urlstr =~ /^https?:\/\/./
+ if @bot.config['url.auto_shorten'] == true and
+ urlstr.length >= @bot.config['url.auto_shorten_min_length']
+ m.reply(bot.plugins['shortenurls'].shorten(nil, {:url=>urlstr, :called=>true}))
+ next
+ end
+ title = nil
+ debug "Getting title for #{urlstr}..."
+ reply = nil
+ begin
+ title = get_title_for_url(urlstr,
+ :always_reply => m.address?,
+ :nick => m.source.nick,
+ :channel => m.channel,
+ :ircline => m.message)
+ debug "Title #{title ? '' : 'not '} found"
+ reply = "#{LINK_INFO} #{title}" if title
+ rescue => e
+ debug e
+ # we might get a 404 because of trailing punctuation, so we try again
+ # with the last character stripped. this might generate invalid URIs
+ # (e.g. because "some.url" gets chopped to some.url%2, so catch that too
+ if e.message =~ /\(404 - Not Found\)/i or e.kind_of?(URI::InvalidURIError)
+ # chop off last non-word character from the unescaped version of
+ # the URL, and retry if we still have enough string to look like a
+ # minimal URL
+ unescaped = URI.unescape(urlstr)
+ debug "Unescaped: #{unescaped}"
+ if unescaped.sub!(/\W$/,'') and unescaped =~ /^https?:\/\/./
+ urlstr.replace URI.escape(unescaped, OUR_UNSAFE)
+ retry
+ else
+ debug "Not retrying #{unescaped}"
+ end
+ end
+ reply = "Error #{e.message}"
+ end
+
+ if display_info > urls_displayed
+ if reply
+ m.reply reply, :overlong => :truncate, :to => :public,
+ :nick => (m.address? ? :auto : false)
+ urls_displayed += 1
+ end
+ end
+
+ next unless list
+
+ # check to see if this url is already listed
+ next if list.find {|u| u.url == urlstr }
+
+ url = Url.new(m.target, m.sourcenick, Time.new, urlstr, title)
+ debug "#{list.length} urls so far"
+ list.pop if list.length > @bot.config['url.max_urls']
+ debug "storing url #{url.url}"
+ list.unshift url
+ debug "#{list.length} urls now"
+ end
+ @registry[m.target] = list
+ end
+
+ def info(m, params)
+ escaped = URI.escape(params[:urls].to_s, OUR_UNSAFE)
+ urls = URI.extract(escaped)
+ Thread.new do
+ handle_urls(m,
+ :urls => urls,
+ :display_info => params[:urls].length,
+ :channels => [])
+ end