#-- vim:sw=2:et #++ # # :title: rbot utilities provider # # Author:: Tom Gilbert # Author:: Giuseppe "Oblomov" Bilotta # # Copyright:: (C) 2002-2006 Tom Gilbert # Copyright:: (C) 2007 Giuseppe Bilotta # # TODO some of these Utils should be rewritten as extensions to the approriate # standard Ruby classes and accordingly be moved to extends.rb require 'net/http' require 'uri' require 'tempfile' begin $we_have_html_entities_decoder = require 'htmlentities' rescue LoadError $we_have_html_entities_decoder = false module ::Irc module Utils UNESCAPE_TABLE = { 'laquo' => '<<', 'raquo' => '>>', 'quot' => '"', 'apos' => '\'', 'micro' => 'u', 'copy' => '(c)', 'trade' => '(tm)', 'reg' => '(R)', '#174' => '(R)', '#8220' => '"', '#8221' => '"', '#8212' => '--', '#39' => '\'', 'amp' => '&', 'lt' => '<', 'gt' => '>', 'hellip' => '...', 'nbsp' => ' ', =begin # extras codes, for future use... 'zwnj' => '‌', 'aring' => '\xe5', 'gt' => '>', 'yen' => '\xa5', 'ograve' => '\xf2', 'Chi' => 'Χ', 'bull' => '•', 'Egrave' => '\xc8', 'Ntilde' => '\xd1', 'upsih' => 'ϒ', 'Yacute' => '\xdd', 'asymp' => '≈', 'radic' => '√', 'otimes' => '⊗', 'nabla' => '∇', 'aelig' => '\xe6', 'oelig' => 'œ', 'equiv' => '≡', 'Psi' => 'Ψ', 'auml' => '\xe4', 'circ' => 'ˆ', 'Acirc' => '\xc2', 'Epsilon' => 'Ε', 'Yuml' => 'Ÿ', 'Eta' => 'Η', 'Icirc' => '\xce', 'Upsilon' => 'Υ', 'ndash' => '–', 'there4' => '∴', 'Prime' => '″', 'prime' => '′', 'psi' => 'ψ', 'Kappa' => 'Κ', 'rsaquo' => '›', 'Tau' => 'Τ', 'darr' => '↓', 'ocirc' => '\xf4', 'lrm' => '‎', 'zwj' => '‍', 'cedil' => '\xb8', 'Ecirc' => '\xca', 'not' => '\xac', 'AElig' => '\xc6', 'oslash' => '\xf8', 'acute' => '\xb4', 'lceil' => '⌈', 'shy' => '\xad', 'rdquo' => '”', 'ge' => '≥', 'Igrave' => '\xcc', 'Ograve' => '\xd2', 'euro' => '€', 'dArr' => '⇓', 'sdot' => '⋅', 'nbsp' => '\xa0', 'lfloor' => '⌊', 'lArr' => '⇐', 'Auml' => '\xc4', 'larr' => '←', 'Atilde' => '\xc3', 'Otilde' => '\xd5', 'szlig' => '\xdf', 'clubs' => '♣', 'diams' => '♦', 'agrave' => '\xe0', 'Ocirc' => '\xd4', 'Iota' => 'Ι', 'Theta' => 'Θ', 'Pi' => 'Π', 'OElig' => 'Œ', 'Scaron' => 'Š', 'frac14' => '\xbc', 'egrave' => '\xe8', 'sub' => '⊂', 'iexcl' => '\xa1', 'frac12' => '\xbd', 'sbquo' => '‚', 'ordf' => '\xaa', 'sum' => '∑', 'prop' => '∝', 'Uuml' => '\xdc', 'ntilde' => '\xf1', 'sup' => '⊃', 'theta' => 'θ', 'prod' => '∏', 'nsub' => '⊄', 'hArr' => '⇔', 'rlm' => '‏', 'THORN' => '\xde', 'infin' => '∞', 'yuml' => '\xff', 'Mu' => 'Μ', 'le' => '≤', 'Eacute' => '\xc9', 'thinsp' => ' ', 'ecirc' => '\xea', 'bdquo' => '„', 'Sigma' => 'Σ', 'fnof' => 'ƒ', 'Aring' => '\xc5', 'tilde' => '˜', 'frac34' => '\xbe', 'emsp' => ' ', 'mdash' => '—', 'uarr' => '↑', 'permil' => '‰', 'Ugrave' => '\xd9', 'rarr' => '→', 'Agrave' => '\xc0', 'chi' => 'χ', 'forall' => '∀', 'eth' => '\xf0', 'rceil' => '⌉', 'iuml' => '\xef', 'gamma' => 'γ', 'lambda' => 'λ', 'harr' => '↔', 'rang' => '〉', 'xi' => 'ξ', 'dagger' => '†', 'divide' => '\xf7', 'Ouml' => '\xd6', 'image' => 'ℑ', 'alefsym' => 'ℵ', 'igrave' => '\xec', 'otilde' => '\xf5', 'Oacute' => '\xd3', 'sube' => '⊆', 'alpha' => 'α', 'frasl' => '⁄', 'ETH' => '\xd0', 'lowast' => '∗', 'Nu' => 'Ν', 'plusmn' => '\xb1', 'Euml' => '\xcb', 'real' => 'ℜ', 'sup1' => '\xb9', 'sup2' => '\xb2', 'sup3' => '\xb3', 'Oslash' => '\xd8', 'Aacute' => '\xc1', 'cent' => '\xa2', 'oline' => '‾', 'Beta' => 'Β', 'perp' => '⊥', 'Delta' => 'Δ', 'loz' => '◊', 'pi' => 'π', 'iota' => 'ι', 'empty' => '∅', 'euml' => '\xeb', 'brvbar' => '\xa6', 'iacute' => '\xed', 'para' => '\xb6', 'micro' => '\xb5', 'cup' => '∪', 'weierp' => '℘', 'uuml' => '\xfc', 'part' => '∂', 'icirc' => '\xee', 'delta' => 'δ', 'omicron' => 'ο', 'upsilon' => 'υ', 'Iuml' => '\xcf', 'Lambda' => 'Λ', 'Xi' => 'Ξ', 'kappa' => 'κ', 'ccedil' => '\xe7', 'Ucirc' => '\xdb', 'cap' => '∩', 'mu' => 'μ', 'scaron' => 'š', 'lsquo' => '‘', 'isin' => '∈', 'Zeta' => 'Ζ', 'supe' => '⊇', 'deg' => '\xb0', 'and' => '∧', 'tau' => 'τ', 'pound' => '\xa3', 'hellip' => '…', 'curren' => '\xa4', 'int' => '∫', 'ucirc' => '\xfb', 'rfloor' => '⌋', 'ensp' => ' ', 'crarr' => '↵', 'ugrave' => '\xf9', 'notin' => '∉', 'exist' => '∃', 'uArr' => '⇑', 'cong' => '≅', 'Dagger' => '‡', 'oplus' => '⊕', 'times' => '\xd7', 'atilde' => '\xe3', 'piv' => 'ϖ', 'ni' => '∋', 'Phi' => 'Φ', 'lsaquo' => '‹', 'Uacute' => '\xda', 'Omicron' => 'Ο', 'ang' => '∠', 'ne' => '≠', 'iquest' => '\xbf', 'eta' => 'η', 'yacute' => '\xfd', 'Rho' => 'Ρ', 'uacute' => '\xfa', 'Alpha' => 'Α', 'zeta' => 'ζ', 'Omega' => 'Ω', 'nu' => 'ν', 'sim' => '∼', 'sect' => '\xa7', 'phi' => 'φ', 'sigmaf' => 'ς', 'macr' => '\xaf', 'minus' => '−', 'Ccedil' => '\xc7', 'ordm' => '\xba', 'epsilon' => 'ε', 'beta' => 'β', 'rArr' => '⇒', 'rho' => 'ρ', 'aacute' => '\xe1', 'eacute' => '\xe9', 'omega' => 'ω', 'middot' => '\xb7', 'Gamma' => 'Γ', 'Iacute' => '\xcd', 'lang' => '〈', 'spades' => '♠', 'rsquo' => '’', 'uml' => '\xa8', 'thorn' => '\xfe', 'ouml' => '\xf6', 'thetasym' => 'ϑ', 'or' => '∨', 'raquo' => '\xbb', 'acirc' => '\xe2', 'ldquo' => '“', 'hearts' => '♥', 'sigma' => 'σ', 'oacute' => '\xf3', =end } end end end module ::Irc # miscellaneous useful functions module Utils SEC_PER_MIN = 60 SEC_PER_HR = SEC_PER_MIN * 60 SEC_PER_DAY = SEC_PER_HR * 24 SEC_PER_MNTH = SEC_PER_DAY * 30 SEC_PER_YR = SEC_PER_MNTH * 12 def Utils.secs_to_string_case(array, var, string, plural) case var when 1 array << "1 #{string}" else array << "#{var} #{plural}" end end # turn a number of seconds into a human readable string, e.g # 2 days, 3 hours, 18 minutes, 10 seconds def Utils.secs_to_string(secs) ret = [] years, secs = secs.divmod SEC_PER_YR secs_to_string_case(ret, years, "year", "years") if years > 0 months, secs = secs.divmod SEC_PER_MNTH secs_to_string_case(ret, months, "month", "months") if months > 0 days, secs = secs.divmod SEC_PER_DAY secs_to_string_case(ret, days, "day", "days") if days > 0 hours, secs = secs.divmod SEC_PER_HR secs_to_string_case(ret, hours, "hour", "hours") if hours > 0 mins, secs = secs.divmod SEC_PER_MIN secs_to_string_case(ret, mins, "minute", "minutes") if mins > 0 secs = secs.to_i secs_to_string_case(ret, secs, "second", "seconds") if secs > 0 or ret.empty? case ret.length when 0 raise "Empty ret array!" when 1 return ret.to_s else return [ret[0, ret.length-1].join(", ") , ret[-1]].join(" and ") end end def Utils.safe_exec(command, *args) IO.popen("-") {|p| if(p) return p.readlines.join("\n") else begin $stderr = $stdout exec(command, *args) rescue Exception => e puts "exec of #{command} led to exception: #{e.inspect}" Kernel::exit! 0 end puts "exec of #{command} failed" Kernel::exit! 0 end } end @@safe_save_dir = nil unless defined?(@@safe_save_dir) def Utils.set_safe_save_dir(str) @@safe_save_dir = str.dup end def Utils.safe_save(file) raise 'No safe save directory defined!' if @@safe_save_dir.nil? basename = File.basename(file) temp = Tempfile.new(basename,@@safe_save_dir) temp.binmode yield temp if block_given? temp.close File.rename(temp.path, file) end # returns a string containing the result of an HTTP GET on the uri def Utils.http_get(uristr, readtimeout=8, opentimeout=4) # ruby 1.7 or better needed for this (or 1.6 and debian unstable) Net::HTTP.version_1_2 # (so we support the 1_1 api anyway, avoids problems) uri = URI.parse uristr query = uri.path if uri.query query += "?#{uri.query}" end proxy_host = nil proxy_port = nil if(ENV['http_proxy'] && proxy_uri = URI.parse(ENV['http_proxy'])) proxy_host = proxy_uri.host proxy_port = proxy_uri.port end begin http = Net::HTTP.new(uri.host, uri.port, proxy_host, proxy_port) http.open_timeout = opentimeout http.read_timeout = readtimeout http.start {|http| resp = http.get(query) if resp.code == "200" return resp.body end } rescue => e # cheesy for now error "Utils.http_get exception: #{e.inspect}, while trying to get #{uristr}" return nil end end def Utils.decode_html_entities(str) if $we_have_html_entities_decoder return HTMLEntities.decode_entities(str) else str.gsub(/(&(.+?);)/) { symbol = $2 # remove the 0-paddng from unicode integers if symbol =~ /#(.+)/ symbol = "##{$1.to_i.to_s}" end # output the symbol's irc-translated character, or a * if it's unknown UNESCAPE_TABLE[symbol] || '*' } end end H1_REGEX = /]*)?>(.*?)<\/h1>/im PAR_REGEX = /]*)?>.*?<\/p>/im # Try to grab and IRCify the first HTML par (

tag) in the given string. # If possible, grab the one after the first h1 heading # # It is possible to pass some options to determine how the stripping # occurs. Currently, only one option is supported: # * :strip => Regex or String to strip at the beginning of the obtained # text # def Utils.ircify_first_html_par(xml, opts={}) txt = String.new strip = opts[:strip] strip = Regexp.new(/^#{Regexp.escape(strip)}/) if strip.kind_of?(String) header_found = xml.match(H1_REGEX) if header_found header_found = $' debug "Found header: #{header_found[1].inspect}" while txt.empty? candidate = header_found[PAR_REGEX] break unless candidate txt = candidate.ircify_html header_found = $' txt.sub!(strip, '') if strip end end # If we haven't found a first par yet, try to get it from the whole # document if txt.empty? header_found = xml while txt.empty? candidate = header_found[PAR_REGEX] break unless candidate txt = candidate.ircify_html header_found = $' txt.sub!(strip, '') if strip end end return txt end # Get the first pars of the first _count_ _urls_. # The pages are downloaded using an HttpUtil service passed as _opts_ :http_util, # and echoed as replies to the IRC message passed as _opts_ :message. # def Utils.get_first_pars(urls, count, opts={}) idx = 0 msg = opts[:message] while count > 0 and urls.length > 0 url = urls.shift idx += 1 # FIXME what happens if some big file is returned? We should share # code with the url plugin to only retrieve partial file content! xml = opts[:http_util].get_cached(url) if xml.nil? debug "Unable to retrieve #{url}" next end par = Utils.ircify_first_html_par(xml, opts) if par.empty? debug "No first par found\n#{xml}" # FIXME only do this if the 'url' plugin is loaded # TODO even better, put the code here # par = @bot.plugins['url'].get_title_from_html(xml) next if par.empty? end msg.reply "[#{idx}] #{par}", :overlong => :truncate if msg count -=1 end end end end