X-Git-Url: https://git.netwichtig.de/gitweb/?a=blobdiff_plain;f=data%2Frbot%2Fplugins%2Ffreshmeat.rb;h=810b82fd8aa4ef2f4106f328bb8e94861642489a;hb=052217de30c59206d7025b582d4604557a747470;hp=49e73e0d2785b447ecef4237240c048765c548db;hpb=109fa2a5b63af113df2c6b21d44135efa0d94d70;p=user%2Fhenk%2Fcode%2Fruby%2Frbot.git diff --git a/data/rbot/plugins/freshmeat.rb b/data/rbot/plugins/freshmeat.rb index 49e73e0d..810b82fd 100644 --- a/data/rbot/plugins/freshmeat.rb +++ b/data/rbot/plugins/freshmeat.rb @@ -1,21 +1,82 @@ +#-- vim:sw=2:et +#++ +# +# :title: Freshmeat plugin for rbot + require 'rexml/document' class FreshmeatPlugin < Plugin include REXML + + Config.register Config::StringValue.new('freshmeat.api_token', + :desc => "Auth token for freshmeat API requests. Without this, no freshmeat calls will be made. Find it in your freshmeat user account settings.", + :default => "") + + def api_token + return @bot.config['freshmeat.api_token'] + end + + # Checks if an API token is configure, warns if not, returns true or false + def check_api_token(m=nil) + if api_token.empty? + if m + m.reply _("you must set the configuration value freshmeat.api_token to a valid freshmeat auth token, otherwise I cannot make requests to the site") + end + return false + end + return true + end + def help(plugin, topic="") "freshmeat search [=4] => search freshmeat for , freshmeat [=4] => return up to freshmeat headlines" end - + + REL_ENTRY = %r{(.*?)} + PRJ_ENTRY = %r{(.*?)} + + # This method defines a filter for fm pages. It's needed because the generic + # summarization grabs a comment, not the actual article. + # + def freshmeat_filter(s) + loc = Utils.check_location(s, /freshmeat\.net/) + return nil unless loc + entries = [] + s[:text].scan(/#{REL_ENTRY}|#{PRJ_ENTRY}/) { |m| + entry = { + :type => ($1 || $4).dup, + :code => ($2 || $5).dup, + :name => ($3 || $6).dup + } + entries << entry + } + return nil if entries.empty? + title = s[:text].ircify_html_title + content = entries.inject([]) { |l, e| l << e[:name] }.join(" | ") + return {:title => title, :content => content} + end + + def initialize + super + @bot.register_filter(:freshmeat, :htmlinfo) { |s| freshmeat_filter(s) } + end + def search_freshmeat(m, params) + return unless check_api_token(m) max = params[:limit].to_i search = params[:search].to_s max = 8 if max > 8 - xml = @bot.httputil.get("http://freshmeat.net/search-xml/?orderby=locate_projectname_full_DESC&q=#{CGI.escape(search)}") + xml = @bot.httputil.get("http://freshmeat.net/search.xml?auth_code=#{api_token}&q=#{CGI.escape(search)}") unless xml - m.reply "search for #{search} failed" + m.reply "search for #{search} failed (is the API token configured correctly?)" return end - doc = Document.new xml + doc = nil + begin + doc = Document.new xml + rescue + debug xml + error $! + end unless doc m.reply "search for #{search} failed" return @@ -25,13 +86,15 @@ class FreshmeatPlugin < Plugin title_width = 0 url_width = 0 done = 0 - doc.elements.each("*/match") {|e| - name = e.elements["projectname_short"].text - url = "http://freshmeat.net/projects/#{name}/" - desc = e.elements["desc_short"].text - title = e.elements["projectname_full"].text - #title_width = title.length if title.length > title_width + doc.elements.each("hash/projects/project") {|e| + title = e.elements["name"].text + title_width = title.length if title.length > title_width + + url = "http://freshmeat.net/projects/#{e.elements['permalink'].text}" url_width = url.length if url.length > url_width + + desc = e.elements["oneliner"].text + matches << [title, url, desc] done += 1 break if done >= max @@ -39,23 +102,38 @@ class FreshmeatPlugin < Plugin if matches.length == 0 m.reply "not found: #{search}" end + + title_width += 2 # for bold + matches.each {|mat| - title = mat[0] + title = Bold + mat[0] + Bold url = mat[1] desc = mat[2] - desc.gsub!(/(.{#{max_width - 3 - url_width}}).*/, '\1..') - reply = sprintf("%s | %s", url.ljust(url_width), desc) - m.reply reply + reply = sprintf("%s | %s | %s", title.ljust(title_width), url.ljust(url_width), desc) + m.reply reply, :overlong => :truncate } end - - def freshmeat(m, params) + + # We do manual parsing so that we can work even with the RSS plugin not loaded + def freshmeat_rss(m, params) max = params[:limit].to_i max = 8 if max > 8 + + text = _("retrieving freshmeat news from the RSS") + reason = "" + case params[:api_token] + when :missing + reason = _(" because no API token is configured") + when :wrong + reason = _(" because the configured API token is wrong") + end + + m.reply text + reason + begin - xml = @bot.httputil.get('http://images.feedstermedia.com/feedcache/ostg/freshmeat/fm-releases-global.xml') + xml = @bot.httputil.get('http://freshmeat.net/?format=atom') unless xml - m.reply "freshmeat news parse failed" + m.reply _("couldn't retrieve freshmeat news feed") return end doc = Document.new xml @@ -64,6 +142,7 @@ class FreshmeatPlugin < Plugin return end rescue + error $! m.reply "freshmeat news parse failed" return end @@ -72,27 +151,96 @@ class FreshmeatPlugin < Plugin max_width = 60 title_width = 0 done = 0 - doc.elements.each("*/channel/item") {|e| - desc = e.elements["description"].text - title = e.elements["title"].text - #title.gsub!(/\s+\(.*\)\s*$/, "") - title.strip! + doc.elements.each("feed/entry") {|e| + # TODO desc should be replaced by the oneliner, but this means one more hit per project + # so we clip out all of the description and leave just the 'changes' part + desc = e.elements["content"].text.ircify_html.sub(/.*?#{Bold}Changes:#{Bold}/,'').strip + title = e.elements["title"].text.ircify_html.strip title_width = title.length if title.length > title_width matches << [title, desc] done += 1 break if done >= max } + title_width += 2 + matches.each {|mat| + title = Bold + mat[0] + Bold + desc = mat[1] + reply = sprintf("%s | %s", title.ljust(title_width), desc) + m.reply reply, :overlong => :truncate + } + end + + def freshmeat(m, params) + # use the RSS if no API token is defined + return freshmeat_rss(m, params.merge(:api_token => :missing)) unless check_api_token + xml = @bot.httputil.get("http://freshmeat.net/index.xml?auth_code=#{api_token}") + # use the RSS if we couldn't get the XML + return freshmeat_rss(m, params.merge(:api_token => :wrong)) unless xml + + max = params[:limit].to_i + max = 8 if max > 8 + begin + doc = Document.new xml + unless doc + m.reply "freshmeat news parse failed" + return + end + rescue + error $! + m.reply "freshmeat news parse failed" + return + end + + matches = Array.new + title_width = 0 + url_width = 0 + time_width = 0 + done = 0 + now = Time.now + doc.elements.each("releases/release") {|e| + approved = e.elements["approved-at"].text.strip + date = Time.parse(approved) rescue nil + timeago = date ? (Utils.timeago(date, :start_date => now) rescue nil) : approved + time_width = timeago.length if timeago.length > time_width + + changelog = e.elements["changelog"].text.ircify_html + + title = e.elements["project/name"].text.ircify_html + title_width = title.length if title.length > title_width + url = "http://freshmeat.net/projects/#{e.elements['project/permalink'].text}" + url_width = url.length if url.length > url_width + + desc = e.elements["project/oneliner"].text.ircify_html + + matches << [title, timeago, desc, url, changelog] + + done += 1 + break if done >= max + } + + if matches.empty? + m.reply _("no news in freshmeat!") + return + end + + title_width += 2 matches.each {|mat| - title = mat[0] - #desc = mat[1] - #desc.gsub!(/(.{#{max_width - 3 - title_width}}).*/, '\1..') - #reply = sprintf("%#{title_width}s | %s", title, desc) - m.reply title + title = Bold + mat[0] + Bold + timeago = mat[1] + desc = mat[2] + url = mat[3] + changelog = mat[4] + reply = sprintf("%s | %s | %s | %s", + timeago.rjust(time_width), + title.ljust(title_width), + url.ljust(url_width), + desc) + m.reply reply, :overlong => :truncate } end end plugin = FreshmeatPlugin.new plugin.map 'freshmeat search :limit *search', :action => 'search_freshmeat', :defaults => {:limit => 4}, :requirements => {:limit => /^\d+$/} -plugin.map 'freshmeat :limit', :defaults => {:limit => 4}, +plugin.map 'freshmeat :limit', :defaults => {:limit => 4}, :requirements => {:limit => /^\d+$/}