]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/commitdiff
RSS plugin is now in '''much''' better shape
authorGiuseppe Bilotta <giuseppe.bilotta@gmail.com>
Mon, 24 Jul 2006 12:33:46 +0000 (12:33 +0000)
committerGiuseppe Bilotta <giuseppe.bilotta@gmail.com>
Mon, 24 Jul 2006 12:33:46 +0000 (12:33 +0000)
data/rbot/plugins/rss.rb

index 12d7f24af1e801328c2ff719b548b9c9e6d6e34c..5964faf526ed0f68407b7d8eab4a8be346176388 100644 (file)
@@ -32,44 +32,77 @@ class ::String
   end\r
 end\r
 \r
+class ::RssBlob\r
+  attr :url\r
+  attr :handle\r
+  attr :type\r
+  attr :watchers\r
+\r
+  def initialize(url,handle=nil,type=nil,watchers=[])\r
+    @url = url\r
+    if handle\r
+      @handle = handle\r
+    else\r
+      @handle = url\r
+    end\r
+    @type = type\r
+    @watchers = watchers\r
+  end\r
+\r
+  def watched?\r
+    !@watchers.empty?\r
+  end\r
+\r
+  def watched_by?(who)\r
+    @watchers.include?(who)\r
+  end\r
+\r
+  def add_watch(who)\r
+    if watched_by?(who)\r
+      return nil\r
+    end\r
+    @watchers << who unless watched_by?(who)\r
+    return who\r
+  end\r
+\r
+  def rm_watch(who)\r
+    @watchers.delete(who)\r
+  end\r
+\r
+  #  def to_ary\r
+  #    [@handle,@url,@type,@watchers]\r
+  #  end\r
+end\r
 \r
 class RSSFeedsPlugin < Plugin\r
   @@watchThreads = Hash.new\r
+  @@mutex = Mutex.new\r
 \r
   # Keep a 1:1 relation between commands and handlers\r
   @@handlers = {\r
     "rss" => "handle_rss",\r
     "addrss" => "handle_addrss",\r
     "rmrss" => "handle_rmrss",\r
+    "rmwatch" => "handle_rmwatch",\r
     "listrss" => "handle_listrss",\r
     "listwatches" => "handle_listrsswatch",\r
     "rewatch" => "handle_rewatch",\r
     "watchrss" => "handle_watchrss",\r
-    "rmwatch" => "handle_rmwatch"\r
   }\r
 \r
   def initialize\r
     super\r
-    @feeds = Hash.new\r
-    @watchList = Hash.new\r
-    begin\r
-      IO.foreach("#{@bot.botclass}/rss/feeds") { |line|\r
-        s = line.chomp.split("|", 2)\r
-        @feeds[s[0]] = s[1]\r
-      }\r
-    rescue\r
-      log "no feeds";\r
-    end\r
-    begin\r
-      IO.foreach("#{@bot.botclass}/rss/watchlist") { |line|\r
-        s = line.chomp.split("|", 3)\r
-        @watchList[s[0]] = [s[1], s[2]]\r
-        watchRss( s[2], s[0], s[1] )\r
-      }\r
-    rescue\r
-      log "no watchlist"\r
+    kill_threads\r
+    if @registry.has_key?(:feeds)\r
+      @feeds = @registry[:feeds]\r
+    else\r
+      @feeds = Hash.new\r
     end\r
+    handle_rewatch\r
+  end\r
 \r
+  def watchlist\r
+    @feeds.select { |h, f| f.watched? }\r
   end\r
 \r
   def cleanup\r
@@ -77,40 +110,36 @@ class RSSFeedsPlugin < Plugin
   end\r
 \r
   def save\r
-    Dir.mkdir("#{@bot.botclass}/rss") if not FileTest.directory?("#{@bot.botclass}/rss")\r
-    File.open("#{@bot.botclass}/rss/feeds", "w") { |file|\r
-      @feeds.each { |k,v|\r
-        file.puts(k + "|" + v)\r
-      }\r
-    }\r
-    File.open("#{@bot.botclass}/rss/watchlist", "w") { |file|\r
-      @watchList.each { |url, d|\r
-        feedFormat = d[0] || ''\r
-        whichChan = d[1] || 'markey'\r
-        file.puts(url + '|' + feedFormat + '|' + whichChan)\r
-      }\r
-    }\r
+    @registry[:feeds] = @feeds\r
   end\r
 \r
   def kill_threads\r
-    Thread.critical=true\r
-    # Abort all running threads.\r
-    @@watchThreads.each { |url, thread|\r
-      debug "Killing thread for #{url}"\r
-      thread.kill\r
+    @@mutex.synchronize {\r
+      # Abort all running threads.\r
+      @@watchThreads.each { |url, thread|\r
+        debug "Killing thread for #{url}"\r
+        thread.kill\r
+      }\r
+      # @@watchThreads.each { |url, thread|\r
+      #   debug "Joining on killed thread for #{url}"\r
+      #   thread.join\r
+      # }\r
+      @@watchThreads = Hash.new\r
     }\r
-    # @@watchThreads.each { |url, thread|\r
-    #   debug "Joining on killed thread for #{url}"\r
-    #   thread.join\r
-    # }\r
-    @@watchThreads = Hash.new\r
-    Thread.critical=false\r
   end\r
 \r
   def help(plugin,topic="")\r
     "RSS Reader: rss name [limit] => read a named feed [limit maximum posts, default 5], addrss [force] name url => add a feed, listrss => list all available feeds, rmrss name => remove the named feed, watchrss url [type] => watch a rss feed for changes (type may be 'amarokblog', 'amarokforum', 'mediawiki', 'gmame' or empty - it defines special formatting of feed items), rewatch => restart all rss watches, rmwatch url => stop watching for changes in url, listwatches => see a list of watched feeds"\r
   end\r
 \r
+  def report_problem(report, m=nil)\r
+      if m\r
+        m.reply report\r
+      else\r
+        warning report\r
+      end\r
+  end\r
+\r
   def privmsg(m)\r
     meth = self.method(@@handlers[m.plugin])\r
     meth.call(m)\r
@@ -132,26 +161,30 @@ class RSSFeedsPlugin < Plugin
     end\r
 \r
     url = ''\r
-    if m.params =~ /^http:\/\//\r
+    if m.params =~ /^https?:\/\//\r
       url = m.params\r
+      @@mutex.synchronize {\r
+        @feeds[url] = RssBlob.new(url)\r
+        feed = @feeds[url]\r
+      }\r
     else\r
-      unless @feeds.has_key?(m.params)\r
+      feed = @feeds.fetch(m.params, nil)\r
+      unless feed\r
         m.reply(m.params + "? what is that feed about?")\r
         return\r
       end\r
-      url = @feeds[m.params]\r
     end\r
 \r
     m.reply("Please wait, querying...")\r
-    title = ''\r
-    items = fetchRSS(m.replyto, url, title)\r
-    if(items == nil)\r
-      return\r
-    end\r
+    title = items = nil\r
+    @@mutex.synchronize {\r
+      title, items = fetchRss(feed, m)\r
+    }\r
+    return unless items\r
     m.reply("Channel : #{title}")\r
-    # FIXME: optional by-date sorting if dates present\r
+    # TODO: optional by-date sorting if dates present\r
     items[0...limit].each do |item|\r
-      printRSSItem(m.replyto,item)\r
+      printRssItem(m.replyto,item)\r
     end\r
   end\r
 \r
@@ -164,57 +197,64 @@ class RSSFeedsPlugin < Plugin
       forced = true\r
       m.params.gsub!(/^force /, '')\r
     end\r
-    feed = m.params.scan(/^(\S+)\s+(\S+)$/)\r
-    unless feed.length == 1 && feed[0].length == 2\r
+    feed = m.params.match(/^(\S+)\s+(\S+)$/)\r
+    if feed.nil?\r
       m.reply("incorrect usage: " + help(m.plugin))\r
-      return\r
     end\r
-    if @feeds.has_key?(feed[0][0]) && !forced\r
-      m.reply("But there is already a feed named #{feed[0][0]} with url #{@feeds[feed[0][0]]}")\r
+    handle = feed[1]\r
+    url = feed[2]\r
+    debug "Handle: #{handle.inspect}, Url: #{url.inspect}"\r
+    if @feeds.fetch(handle, nil) && !forced\r
+      m.reply("But there is already a feed named #{handle} with url #{@feeds[handle].url}")\r
       return\r
     end\r
-    feed[0][0].gsub!("|", '_')\r
-    @feeds[feed[0][0]] = feed[0][1]\r
-    m.reply("RSS: Added #{feed[0][1]} with name #{feed[0][0]}")\r
+    handle.gsub!("|", '_')\r
+    @@mutex.synchronize {\r
+      @feeds[handle] = RssBlob.new(url,handle)\r
+    }\r
+    m.reply "RSS: Added #{url} with name #{handle}"\r
+    return handle\r
   end\r
 \r
   def handle_rmrss(m)\r
-    unless m.params\r
-      m.reply "incorrect usage: " + help(m.plugin)\r
+    feed = handle_rmwatch(m, true)\r
+    if feed.watched?\r
+      m.reply "someone else is watching #{feed.handle}, I won't remove it from my list"\r
       return\r
     end\r
-    unless @feeds.has_key?(m.params)\r
-      m.reply("dunno that feed")\r
-      return\r
-    end\r
-    @feeds.delete(m.params)\r
-    @bot.okay(m.replyto)\r
+    @@mutex.synchronize {\r
+      @feeds.delete(feed.handle)\r
+    }\r
+    m.okay\r
+    return\r
   end\r
 \r
-  def handle_rmwatch(m)\r
+  def handle_rmwatch(m,pass=false)\r
     unless m.params\r
       m.reply "incorrect usage: " + help(m.plugin)\r
       return\r
     end\r
-    unless @watchList.has_key?(m.params)\r
-      m.reply("no such watch")\r
+    handle = m.params\r
+    unless @feeds.has_key?(handle)\r
+      m.reply("dunno that feed")\r
       return\r
     end\r
-    unless @watchList[m.params][1] == m.replyto\r
-      m.reply("no such watch for this channel/nick")\r
-      return\r
+    feed = @feeds[handle]\r
+    if feed.rm_watch(m.replyto)\r
+      m.reply "#{m.replyto} has been removed from the watchlist for #{feed.handle}"\r
+    else\r
+      m.reply("#{m.replyto} wasn't watching #{feed.handle}") unless pass\r
     end\r
-    @watchList.delete(m.params)\r
-    Thread.critical=true\r
-    if @@watchThreads[m.params].kind_of? Thread\r
-      @@watchThreads[m.params].kill\r
-      debug "rmwatch: Killed thread for #{m.params}"\r
-      # @@watchThreads[m.params].join\r
-      # debug "rmwatch: Joined killed thread for #{m.params}"\r
-      @@watchThreads.delete(m.params)\r
+    if !feed.watched?\r
+      @@mutex.synchronize {\r
+        if @@watchThreads[handle].kind_of? Thread\r
+          @@watchThreads[handle].kill\r
+          debug "rmwatch: Killed thread for #{handle}"\r
+          @@watchThreads.delete(handle)\r
+        end\r
+      }\r
     end\r
-    Thread.critical=false\r
-    @bot.okay(m.replyto)\r
+    return feed\r
   end\r
 \r
   def handle_listrss(m)\r
@@ -222,35 +262,39 @@ class RSSFeedsPlugin < Plugin
     if @feeds.length == 0\r
       reply = "No feeds yet."\r
     else\r
-      @feeds.each { |k,v|\r
-        reply << k + ": " + v + "\n"\r
+      @@mutex.synchronize {\r
+        @feeds.each { |handle, feed|\r
+          reply << "#{feed.handle}: #{feed.url} (in format: #{feed.type ? feed.type : 'default'})"\r
+          (reply << " (watched)") if feed.watched_by?(m.replyto)\r
+          reply << "\n"\r
+          debug reply\r
+        }\r
       }\r
     end\r
-    m.reply(reply)\r
+    m.reply reply\r
   end\r
 \r
   def handle_listrsswatch(m)\r
     reply = ''\r
-    if @watchList.length == 0\r
+    if watchlist.length == 0\r
       reply = "No watched feeds yet."\r
     else\r
-      @watchList.each { |url,v|\r
-        reply << url + " for " + v[1] + " (in format: " + (v[0]?v[0]:"default") + ")\n"\r
+      watchlist.each { |handle, feed|\r
+        (reply << "#{feed.handle}: #{feed.url} (in format: #{feed.type ? feed.type : 'default'})\n") if feed.watched_by?(m.replyto)\r
+        debug reply\r
       }\r
     end\r
-    m.reply(reply)\r
+    m.reply reply\r
   end\r
 \r
-  def handle_rewatch(m)\r
+  def handle_rewatch(m=nil)\r
     kill_threads\r
 \r
     # Read watches from list.\r
-    @watchList.each{ |url, d|\r
-      feedFormat = d[0]\r
-      whichChan = d[1]\r
-      watchRss(whichChan, url,feedFormat)\r
+    watchlist.each{ |handle, feed|\r
+      watchRss(feed, m)\r
     }\r
-    @bot.okay(m.replyto)\r
+    m.okay if m\r
   end\r
 \r
   def handle_watchrss(m)\r
@@ -258,61 +302,68 @@ class RSSFeedsPlugin < Plugin
       m.reply "incorrect usage: " + help(m.plugin)\r
       return\r
     end\r
-    feed = m.params.scan(/(\S+)/)\r
-    debug feed.inspect\r
+    if m.params =~ /\s+/\r
+      handle = handle_addrss(m)\r
+    else\r
+      handle = m.params\r
+    end\r
+    feed = nil\r
+    @@mutex.synchronize {\r
+      feed = @feeds.fetch(handle, nil)\r
+    }\r
     if feed\r
-      url = feed[0]\r
-      feedFormat = ""\r
-      feedFormat = feed[1] if feed.length > 1\r
-      if @watchList.has_key?(url)\r
-        m.reply("But there is already a watch for feed #{url} on chan #{@watchList[url][1]}")\r
-        return\r
-      end\r
-      @watchList[url] = [feedFormat, m.replyto]\r
-      watchRss(m.replyto, url,feedFormat)\r
-      m.okay\r
+      @@mutex.synchronize {\r
+        if feed.add_watch(m.replyto)\r
+          watchRss(feed, m)\r
+          m.okay\r
+        else\r
+          m.reply "Already watching #{feed.handle}"\r
+        end\r
+      }\r
     else\r
-      m.reply "Wrong syntax"\r
+      m.reply "Couldn't watch feed #{handle} (no such feed found)"\r
     end\r
   end\r
 \r
   private\r
-  def watchRss(whichChan, url, feedFormat)\r
-    if @@watchThreads.has_key?(url)\r
-      @bot.say whichChan, "ERROR: watcher thread for #{url} is already running! #{@@watchThreads[url]}"\r
+  def watchRss(feed, m=nil)\r
+    if @@watchThreads.has_key?(feed.handle)\r
+      report_problem("watcher thread for #{feed.handle} is already running", m)\r
       return\r
     end\r
-    @@watchThreads[url] = Thread.new do\r
+    @@watchThreads[feed.handle] = Thread.new do\r
       debug 'watchRss thread started.'\r
       oldItems = []\r
       firstRun = true\r
       loop do\r
         begin\r
-          title = ''\r
-          debug 'Fetching rss feed..'\r
-          newItems = fetchRSS(whichChan, url, title)\r
-          if( newItems.empty? )\r
-            @bot.say whichChan, "Oops - Item is empty"\r
+          debug 'Fetching rss feed...'\r
+          title = newItems = nil\r
+          @@mutex.synchronize {\r
+            title, newItems = fetchRss(feed)\r
+          }\r
+          unless newItems\r
+            m.reply "no items in feed"\r
             break\r
           end\r
           debug "Checking if new items are available"\r
-          if (firstRun)\r
+          if firstRun\r
+            debug "First run, we'll see next time"\r
             firstRun = false\r
           else\r
-            newItems.each do |nItem|\r
-              showItem = true;\r
-              oldItems.each do |oItem|\r
-                if (nItem.to_s == oItem.to_s)\r
-                  showItem = false\r
-                end\r
-              end\r
-              if showItem\r
-                debug "showing #{nItem.title}"\r
-                printFormatedRSS(whichChan, nItem,feedFormat)\r
-              else\r
-                debug "not showing  #{nItem.title}"\r
-                break\r
-              end\r
+            dispItems = newItems.reject { |item|\r
+              oldItems.include?(item)\r
+            }\r
+            if dispItems.length > 0\r
+              debug "Found #{dispItems.length} new items"\r
+              dispItems.each { |item|\r
+                debug "showing #{item.title}"\r
+                @@mutex.synchronize {\r
+                  printFormattedRss(feed.watchers, item, feed.type)\r
+                }\r
+              }\r
+            else\r
+              debug "No new items found"\r
             end\r
           end\r
           oldItems = newItems\r
@@ -328,42 +379,44 @@ class RSSFeedsPlugin < Plugin
     end\r
   end\r
 \r
-  def printRSSItem(whichChan,item)\r
+  def printRssItem(loc,item)\r
     if item.kind_of?(RSS::RDF::Item)\r
-      @bot.say whichChan, item.title.chomp.riphtml.shorten(20) + " @ " + item.link\r
+      @bot.say loc, item.title.chomp.riphtml.shorten(20) + " @ " + item.link\r
     else\r
-      @bot.say whichChan, "#{item.pubDate.to_s.chomp+": " if item.pubDate}#{item.title.chomp.riphtml.shorten(20)+" :: " if item.title}#{" @ "+item.link.chomp if item.link}"\r
+      @bot.say loc, "#{item.pubDate.to_s.chomp+": " if item.pubDate}#{item.title.chomp.riphtml.shorten(20)+" :: " if item.title}#{" @ "+item.link.chomp if item.link}"\r
     end\r
   end\r
 \r
-  def printFormatedRSS(whichChan,item, type)\r
-    case type\r
-    when 'amarokblog'\r
-      @bot.say whichChan, "::#{item.category.content} just blogged at #{item.link}::"\r
-      @bot.say whichChan, "::#{item.title.chomp.riphtml} - #{item.description.chomp.riphtml.shorten(60)}::"\r
-    when 'amarokforum'\r
-      @bot.say whichChan, "::Forum:: #{item.pubDate.to_s.chomp+": " if item.pubDate}#{item.title.chomp.riphtml+" :: " if item.title}#{" @ "+item.link.chomp if item.link}"\r
-    when 'mediawiki'\r
-      @bot.say whichChan, "::Wiki:: #{item.title} has been edited by #{item.dc_creator}. #{item.description.split("\n")[0].chomp.riphtml.shorten(60)} #{item.link} ::"\r
-      debug "mediawiki #{item.title}"\r
-    when "gmame"\r
-      @bot.say whichChan, "::amarok-devel:: Message #{item.title} sent by #{item.dc_creator}. #{item.description.split("\n")[0].chomp.riphtml.shorten(60)}::"\r
-    else\r
-      printRSSItem(whichChan,item)\r
-    end\r
+  def printFormattedRss(locs, item, type)\r
+    locs.each { |loc|\r
+      case type\r
+      when 'amarokblog'\r
+        @bot.say loc, "::#{item.category.content} just blogged at #{item.link}::"\r
+        @bot.say loc, "::#{item.title.chomp.riphtml} - #{item.description.chomp.riphtml.shorten(60)}::"\r
+      when 'amarokforum'\r
+        @bot.say loc, "::Forum:: #{item.pubDate.to_s.chomp+": " if item.pubDate}#{item.title.chomp.riphtml+" :: " if item.title}#{" @ "+item.link.chomp if item.link}"\r
+      when 'mediawiki'\r
+        @bot.say loc, "::Wiki:: #{item.title} has been edited by #{item.dc_creator}. #{item.description.split("\n")[0].chomp.riphtml.shorten(60)} #{item.link} ::"\r
+        debug "mediawiki #{item.title}"\r
+      when "gmame"\r
+        @bot.say loc, "::amarok-devel:: Message #{item.title} sent by #{item.dc_creator}. #{item.description.split("\n")[0].chomp.riphtml.shorten(60)}::"\r
+      else\r
+        printRSSItem(loc,item)\r
+      end\r
+    }\r
   end\r
 \r
-  def fetchRSS(whichChan, url, title)\r
+  def fetchRss(feed, m=nil)\r
     begin\r
       # Use 60 sec timeout, cause the default is too low\r
-      xml = @bot.httputil.get_cached(url,60,60)\r
+      xml = @bot.httputil.get_cached(feed.url,60,60)\r
     rescue URI::InvalidURIError, URI::BadURIError => e\r
-      @bot.say whichChan, "invalid rss feed #{url}"\r
+      report_problem("invalid rss feed #{feed.url}", m)\r
       return\r
     end\r
     debug 'fetched'\r
     unless xml\r
-      @bot.say whichChan, "reading feed #{url} failed"\r
+      report_problem("reading feed #{url} failed", m)\r
       return\r
     end\r
 \r
@@ -376,30 +429,30 @@ class RSSFeedsPlugin < Plugin
       begin\r
         rss = RSS::Parser.parse(xml, false)\r
       rescue RSS::Error\r
-        @bot.say whichChan, "parsing rss stream failed, whoops =("\r
+        report_problem("parsing rss stream failed, whoops =(", m)\r
         return\r
       end\r
     rescue RSS::Error\r
-      @bot.say whichChan, "parsing rss stream failed, oioi"\r
+      report_problem("parsing rss stream failed, oioi", m)\r
       return\r
     rescue => e\r
-      @bot.say whichChan, "processing error occured, sorry =("\r
+      report_problem("processing error occured, sorry =(", m)\r
       debug e.inspect\r
       debug e.backtrace.join("\n")\r
       return\r
     end\r
     items = []\r
     if rss.nil?\r
-      @bot.say whichChan, "#{m.params} does not include RSS 1.0 or 0.9x/2.0"\r
+      report_problem("#{feed.url} does not include RSS 1.0 or 0.9x/2.0",m)\r
     else\r
       begin\r
         rss.output_encoding = "euc-jp"\r
       rescue RSS::UnknownConvertMethod\r
-        @bot.say whichChan, "bah! something went wrong =("\r
+        report_problem("bah! something went wrong =(",m)\r
         return\r
       end\r
       rss.channel.title ||= "Unknown"\r
-      title.replace(rss.channel.title)\r
+      title = rss.channel.title\r
       rss.items.each do |item|\r
         item.title ||= "Unknown"\r
         items << item\r
@@ -407,10 +460,10 @@ class RSSFeedsPlugin < Plugin
     end\r
 \r
     if items.empty?\r
-      @bot.say whichChan, "no items found in the feed, maybe try weed?"\r
+      report_problem("no items found in the feed, maybe try weed?",m)\r
       return\r
     end\r
-    return items\r
+    return [title, items]\r
   end\r
 end\r
 \r
@@ -418,9 +471,9 @@ plugin = RSSFeedsPlugin.new
 plugin.register("rss")\r
 plugin.register("addrss")\r
 plugin.register("rmrss")\r
+plugin.register("rmwatch")\r
 plugin.register("listrss")\r
 plugin.register("rewatch")\r
 plugin.register("watchrss")\r
 plugin.register("listwatches")\r
-plugin.register("rmwatch")\r
 \r