1 # RSS feed plugin for RubyBot
\r
2 # (c) 2004 Stanislav Karchebny <berkus@madfire.net>
\r
3 # (c) 2005 Ian Monroe <ian@monroe.nu>
\r
4 # (c) 2005 Mark Kretschmann <markey@web.de>
\r
5 # Licensed under MIT License.
\r
10 require 'rss/dublincore'
\r
12 # require 'rss/dublincore/2.0'
\r
14 warning "Unable to load RSS libraries, RSS plugin functionality crippled"
\r
19 if self.length > limit
\r
20 self+". " =~ /^(.{#{limit}}[^.!;?]*[.!;?])/mi
\r
27 self.gsub(/<[^>]+>/, '').gsub(/&/,'&').gsub(/"/,'"').gsub(/</,'<').gsub(/>/,'>').gsub(/&ellip;/,'...').gsub(/'/, "'").gsub("\n",'')
\r
31 self.gsub(/'/, "''")
\r
41 def initialize(url,handle=nil,type=nil,watchers=[])
\r
49 @watchers = watchers
\r
56 def watched_by?(who)
\r
57 @watchers.include?(who)
\r
64 @watchers << who unless watched_by?(who)
\r
69 @watchers.delete(who)
\r
73 # [@handle,@url,@type,@watchers]
\r
77 class RSSFeedsPlugin < Plugin
\r
78 @@watchThreads = Hash.new
\r
81 # Keep a 1:1 relation between commands and handlers
\r
83 "rss" => "handle_rss",
\r
84 "addrss" => "handle_addrss",
\r
85 "rmrss" => "handle_rmrss",
\r
86 "rmwatch" => "handle_rmwatch",
\r
87 "listrss" => "handle_listrss",
\r
88 "listwatches" => "handle_listrsswatch",
\r
89 "rewatch" => "handle_rewatch",
\r
90 "watchrss" => "handle_watchrss",
\r
96 if @registry.has_key?(:feeds)
\r
97 @feeds = @registry[:feeds]
\r
105 @feeds.select { |h, f| f.watched? }
\r
113 @registry[:feeds] = @feeds
\r
117 @@mutex.synchronize {
\r
118 # Abort all running threads.
\r
119 @@watchThreads.each { |url, thread|
\r
120 debug "Killing thread for #{url}"
\r
123 # @@watchThreads.each { |url, thread|
\r
124 # debug "Joining on killed thread for #{url}"
\r
127 @@watchThreads = Hash.new
\r
131 def help(plugin,topic="")
\r
132 "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
135 def report_problem(report, m=nil)
\r
144 meth = self.method(@@handlers[m.plugin])
\r
150 m.reply("incorrect usage: " + help(m.plugin))
\r
154 if m.params =~ /\s+(\d+)$/
\r
156 if limit < 1 || limit > 15
\r
157 m.reply("weird, limit not in [1..15], reverting to default")
\r
160 m.params.gsub!(/\s+\d+$/, '')
\r
164 if m.params =~ /^https?:\/\//
\r
166 @@mutex.synchronize {
\r
167 @feeds[url] = RssBlob.new(url)
\r
171 feed = @feeds.fetch(m.params, nil)
\r
173 m.reply(m.params + "? what is that feed about?")
\r
178 m.reply("Please wait, querying...")
\r
179 title = items = nil
\r
180 @@mutex.synchronize {
\r
181 title, items = fetchRss(feed, m)
\r
183 return unless items
\r
184 m.reply("Channel : #{title}")
\r
185 # TODO: optional by-date sorting if dates present
\r
186 items[0...limit].each do |item|
\r
187 printRssItem(m.replyto,item)
\r
191 def handle_addrss(m)
\r
193 m.reply "incorrect usage: " + help(m.plugin)
\r
196 if m.params =~ /^force /
\r
198 m.params.gsub!(/^force /, '')
\r
200 feed = m.params.match(/^(\S+)\s+(\S+)$/)
\r
202 m.reply("incorrect usage: " + help(m.plugin))
\r
206 debug "Handle: #{handle.inspect}, Url: #{url.inspect}"
\r
207 if @feeds.fetch(handle, nil) && !forced
\r
208 m.reply("But there is already a feed named #{handle} with url #{@feeds[handle].url}")
\r
211 handle.gsub!("|", '_')
\r
212 @@mutex.synchronize {
\r
213 @feeds[handle] = RssBlob.new(url,handle)
\r
215 m.reply "RSS: Added #{url} with name #{handle}"
\r
219 def handle_rmrss(m)
\r
220 feed = handle_rmwatch(m, true)
\r
222 m.reply "someone else is watching #{feed.handle}, I won't remove it from my list"
\r
225 @@mutex.synchronize {
\r
226 @feeds.delete(feed.handle)
\r
232 def handle_rmwatch(m,pass=false)
\r
234 m.reply "incorrect usage: " + help(m.plugin)
\r
238 unless @feeds.has_key?(handle)
\r
239 m.reply("dunno that feed")
\r
242 feed = @feeds[handle]
\r
243 if feed.rm_watch(m.replyto)
\r
244 m.reply "#{m.replyto} has been removed from the watchlist for #{feed.handle}"
\r
246 m.reply("#{m.replyto} wasn't watching #{feed.handle}") unless pass
\r
249 @@mutex.synchronize {
\r
250 if @@watchThreads[handle].kind_of? Thread
\r
251 @@watchThreads[handle].kill
\r
252 debug "rmwatch: Killed thread for #{handle}"
\r
253 @@watchThreads.delete(handle)
\r
260 def handle_listrss(m)
\r
262 if @feeds.length == 0
\r
263 reply = "No feeds yet."
\r
265 @@mutex.synchronize {
\r
266 @feeds.each { |handle, feed|
\r
267 reply << "#{feed.handle}: #{feed.url} (in format: #{feed.type ? feed.type : 'default'})"
\r
268 (reply << " (watched)") if feed.watched_by?(m.replyto)
\r
277 def handle_listrsswatch(m)
\r
279 if watchlist.length == 0
\r
280 reply = "No watched feeds yet."
\r
282 watchlist.each { |handle, feed|
\r
283 (reply << "#{feed.handle}: #{feed.url} (in format: #{feed.type ? feed.type : 'default'})\n") if feed.watched_by?(m.replyto)
\r
290 def handle_rewatch(m=nil)
\r
293 # Read watches from list.
\r
294 watchlist.each{ |handle, feed|
\r
300 def handle_watchrss(m)
\r
302 m.reply "incorrect usage: " + help(m.plugin)
\r
305 if m.params =~ /\s+/
\r
306 handle = handle_addrss(m)
\r
311 @@mutex.synchronize {
\r
312 feed = @feeds.fetch(handle, nil)
\r
315 @@mutex.synchronize {
\r
316 if feed.add_watch(m.replyto)
\r
320 m.reply "Already watching #{feed.handle}"
\r
324 m.reply "Couldn't watch feed #{handle} (no such feed found)"
\r
329 def watchRss(feed, m=nil)
\r
330 if @@watchThreads.has_key?(feed.handle)
\r
331 report_problem("watcher thread for #{feed.handle} is already running", m)
\r
334 @@watchThreads[feed.handle] = Thread.new do
\r
335 debug 'watchRss thread started.'
\r
340 debug 'Fetching rss feed...'
\r
341 title = newItems = nil
\r
342 @@mutex.synchronize {
\r
343 title, newItems = fetchRss(feed)
\r
346 m.reply "no items in feed"
\r
349 debug "Checking if new items are available"
\r
351 debug "First run, we'll see next time"
\r
354 dispItems = newItems.reject { |item|
\r
355 oldItems.include?(item)
\r
357 if dispItems.length > 0
\r
358 debug "Found #{dispItems.length} new items"
\r
359 dispItems.each { |item|
\r
360 debug "showing #{item.title}"
\r
361 @@mutex.synchronize {
\r
362 printFormattedRss(feed.watchers, item, feed.type)
\r
366 debug "No new items found"
\r
369 oldItems = newItems
\r
370 rescue Exception => e
\r
371 error "IO failed: #{e.inspect}"
\r
372 debug e.backtrace.join("\n")
\r
375 seconds = 150 + rand(100)
\r
376 debug "Thread going to sleep #{seconds} seconds.."
\r
382 def printRssItem(loc,item)
\r
383 if item.kind_of?(RSS::RDF::Item)
\r
384 @bot.say loc, item.title.chomp.riphtml.shorten(20) + " @ " + item.link
\r
386 @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
390 def printFormattedRss(locs, item, type)
\r
394 @bot.say loc, "::#{item.category.content} just blogged at #{item.link}::"
\r
395 @bot.say loc, "::#{item.title.chomp.riphtml} - #{item.description.chomp.riphtml.shorten(60)}::"
\r
397 @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
399 @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
400 debug "mediawiki #{item.title}"
\r
402 @bot.say loc, "::amarok-devel:: Message #{item.title} sent by #{item.dc_creator}. #{item.description.split("\n")[0].chomp.riphtml.shorten(60)}::"
\r
404 printRssItem(loc,item)
\r
409 def fetchRss(feed, m=nil)
\r
411 # Use 60 sec timeout, cause the default is too low
\r
412 xml = @bot.httputil.get_cached(feed.url,60,60)
\r
413 rescue URI::InvalidURIError, URI::BadURIError => e
\r
414 report_problem("invalid rss feed #{feed.url}", m)
\r
419 report_problem("reading feed #{url} failed", m)
\r
424 ## do validate parse
\r
425 rss = RSS::Parser.parse(xml)
\r
427 rescue RSS::InvalidRSSError
\r
428 ## do non validate parse for invalid RSS 1.0
\r
430 rss = RSS::Parser.parse(xml, false)
\r
432 report_problem("parsing rss stream failed, whoops =(", m)
\r
436 report_problem("parsing rss stream failed, oioi", m)
\r
439 report_problem("processing error occured, sorry =(", m)
\r
441 debug e.backtrace.join("\n")
\r
446 report_problem("#{feed.url} does not include RSS 1.0 or 0.9x/2.0",m)
\r
449 rss.output_encoding = "euc-jp"
\r
450 rescue RSS::UnknownConvertMethod
\r
451 report_problem("bah! something went wrong =(",m)
\r
454 rss.channel.title ||= "Unknown"
\r
455 title = rss.channel.title
\r
456 rss.items.each do |item|
\r
457 item.title ||= "Unknown"
\r
463 report_problem("no items found in the feed, maybe try weed?",m)
\r
466 return [title, items]
\r
470 plugin = RSSFeedsPlugin.new
\r
471 plugin.register("rss")
\r
472 plugin.register("addrss")
\r
473 plugin.register("rmrss")
\r
474 plugin.register("rmwatch")
\r
475 plugin.register("listrss")
\r
476 plugin.register("rewatch")
\r
477 plugin.register("watchrss")
\r
478 plugin.register("listwatches")
\r