require 'rss'
-# Try to load rss/content/2.0 so we can access the data in <content:encoded>
+# Try to load rss/content/2.0 so we can access the data in <content:encoded>
# tags.
begin
require 'rss/content/2.0'
:dc_creator => %w{dc_creator}
}.each { |name, chain| def_bang name, chain }
+ def categories!
+ return nil unless self.respond_to? :categories
+ cats = categories.map do |c|
+ blank2nil { c.content rescue c rescue nil }
+ end.compact
+ cats.empty? ? nil : cats
+ end
+
protected
def blank2nil(&block)
x = yield
:default => 0,
:desc => "Don't announce watched feed if these many seconds elapsed since the last successful update")
+ Config.register Config::IntegerValue.new('rss.announce_max',
+ :default => 3,
+ :desc => "Maximum number of new items to announce when a watched feed is updated")
+
Config.register Config::BooleanValue.new('rss.show_updated',
:default => true,
:desc => "Whether feed items for which the description was changed should be shown as new")
:default => true,
:desc => "Whether to display links from the text of a feed item.")
+ Config.register Config::EnumValue.new('rss.announce_method',
+ :values => ['say', 'notice'],
+ :default => 'say',
+ :desc => "Whether to display links from the text of a feed item.")
+
# Make an 'unique' ID for a given item, based on appropriate bot options
# Currently only suppored is bot.config['rss.show_updated']: when false,
# only the guid/link is accounted for.
-
+
def make_uid(item)
uid = [item.guid! || item.link!]
if @bot.config['rss.show_updated']
# Auxiliary method used to collect two lines for rss output filters,
# running substitutions against DataStream _s_ optionally joined
- # with hash _h_
+ # with hash _h_.
+ #
+ # For substitutions, *_wrap keys can be used to alter the content of
+ # other nonempty keys. If the value of *_wrap is a String, it will be
+ # put before and after the corresponding key; if it's an Array, the first
+ # and second elements will be used for wrapping; if it's nil, no wrapping
+ # will be done (useful to override a default wrapping).
+ #
+ # For example:
+ # :handle_wrap => '::'::
+ # will wrap s[:handle] by prefixing and postfixing it with '::'
+ # :date_wrap => [nil, ' :: ']::
+ # will put ' :: ' after s[:date]
def make_stream(line1, line2, s, h={})
ss = s.merge(h)
- DataStream.new([line1, line2].compact.join("\n") % ss, ss)
+ subs = {}
+ wraps = {}
+ ss.each do |k, v|
+ kk = k.to_s.chomp!('_wrap')
+ if kk
+ nk = kk.intern
+ case v
+ when String
+ wraps[nk] = ss[nk].wrap_nonempty(v, v)
+ when Array
+ wraps[nk] = ss[nk].wrap_nonempty(*v)
+ when nil
+ # do nothing
+ else
+ warning "ignoring #{v.inspect} wrapping of unknown class"
+ end
+ else
+ subs[k] = v
+ end
+ end
+ subs.merge! wraps
+ DataStream.new([line1, line2].compact.join("\n") % subs, ss)
+ end
+
+ # Auxiliary method used to define rss output filters
+ def rss_type(key, &block)
+ @bot.register_filter(key, @outkey, &block)
end
- # Define default RSS filters
+ # Define default output filters (rss types), and load custom ones.
+ # Custom filters are looked for in the plugin's default filter locations
+ # and in rss/types.rb under botclass.
+ # Preferably, the rss_type method should be used in these files, e.g.:
+ # rss_type :my_type do |s|
+ # line1 = "%{handle} and some %{author} info"
+ # make_stream(line1, nil, s)
+ # end
+ # to define the new type 'my_type'. The keys available in the DataStream
+ # are:
+ # item::
+ # the actual rss item
+ # handle::
+ # the item handle
+ # date::
+ # the item date
+ # title::
+ # the item title
+ # desc, link, category, author::
+ # the item description, link, category, author
+ # at::
+ # the string ' @ ' if the item has both an title and a link
+ # handle_wrap, date_wrap, title_wrap, ...::
+ # these keys can be defined to wrap the corresponding elements if they
+ # are nonempty. By default handle is wrapped with '::', date has a ' ::'
+ # appended and title is enbolden
#
- # TODO: load personal ones
def define_filters
- @outkey = :"rss.out"
- @bot.register_filter(:headlines, @outkey) { |s|
- line1 = (s[:handle].empty? ? "%{date}" : "%{handle}") << "%{title}"
- make_stream(line1, nil, s)
- }
- @bot.register_filter(:blog, @outkey) { |s|
- author = s[:author] ? (s[:author] + " ") : ""
- abt = s[:category] ? "about #{s[:category]} " : ""
- line1 = "%{handle}%{date}%{author}blogged %{abt}at %{link}"
- line2 = "%{handle}%{title} - %{desc}"
- make_stream(line1, line2, s, :author => author, :abt => abt)
- }
- @bot.register_filter(:photoblog, @outkey) { |s|
- author = s[:author] ? (s[:author] + " ") : ""
- abt = s[:category] ? "under #{s[:category]} " : ""
- line1 = "%{handle}%{date}%{author}added an image %{abt}at %{link}"
- line2 = "%{handle}%{title} - %{desc}"
- make_stream(line1, line2, s, :author => author, :abt => abt)
- }
- @bot.register_filter(:news, @outkey) { |s|
- line1 = "%{handle}%{date}%{title}%{at}%{link}" % s
- line2 = "%{handle}%{date}%{desc}" % s
- make_stream(line1, line2, s)
- }
- @bot.register_filter(:git, @outkey) { |s|
- author = s[:author].sub(/@\S+?\s*>/, "@...>") + " " if s[:author]
- line1 = "%{handle}%{date}%{author}committed %{title}%{at}%{link}"
- make_stream(line1, nil, s, :author => author)
- }
- @bot.register_filter(:forum, @outkey) { |s|
- line1 = "%{handle}%{date}%{title}%{at}%{link}"
- make_stream(line1, nil, s)
- }
- @bot.register_filter(:wiki, @outkey) { |s|
- line1 = "%{handle}%{date}%{title}%{at}%{link}"
- line1 << "has been edited by %{author}. %{desc}"
- make_stream(line1, nil, s)
- }
- @bot.register_filter(:gmane, @outkey) { |s|
- line1 = "%{handle}%{date}Message %{title} sent by %{author}. %{desc}"
- make_stream(line1, nil, s)
- }
- @bot.register_filter(:trac, @outkey) { |s|
- author = s[:author].sub(/@\S+?\s*>/, "@...>") + ": " if s[:author]
- line1 = "%{handle}%{date}%{author}%{title}%{at}%{link}"
- line2 = nil
- unless s[:item].title =~ /^(?:Changeset \[(?:[\da-f]+)\]|\(git commit\))/
- line2 = "%{handle}%{date}%{desc}"
- end
- make_stream(line1, line2, s, :author => author)
- }
- @bot.register_filter(:"/.", @outkey) { |s|
- dept = "(from the #{s[:item].slash_department} dept) " rescue nil
- sec = " in section #{s[:item].slash_section}" rescue nil
- line1 = "%{handle}%{date}%{dept}%{title}%{at}%{link} "
- line1 << "(posted by %{author}%{sec})"
- make_stream(line1, nil, s, :dept => dept, :sec => sec)
- }
- @bot.register_filter(:default, @outkey) { |s|
- line1 = "%{handle}%{date}%{title}%{at}%{link}"
- line1 << " (by %{author})" if s[:author]
- make_stream(line1, nil, s)
- }
+ @outkey ||= :"rss.out"
- # Define an HTML info filter too
+ # Define an HTML info filter
@bot.register_filter(:rss, :htmlinfo) { |s| htmlinfo_filter(s) }
-
# This is the output format used by the input filter
- @bot.register_filter(:htmlinfo, @outkey) { |s|
+ rss_type :htmlinfo do |s|
line1 = "%{title}%{at}%{link}"
make_stream(line1, nil, s)
- }
+ end
+
+ # the default filter
+ rss_type :default do |s|
+ line1 = "%{handle}%{date}%{title}%{at}%{link}"
+ line1 << " (by %{author})" if s[:author]
+ make_stream(line1, nil, s)
+ end
+
+ @user_types ||= datafile 'types.rb'
+ load_filters
+ load_filters :path => @user_types
end
FEED_NS = %r{xmlns.*http://(purl\.org/rss|www.w3c.org/1999/02/22-rdf)}
m.reply "Channel : #{title}"
disp.each do |item|
- printFormattedRss(feed, item, {:places=>[m.replyto],:handle=>nil,:date=>true})
+ printFormattedRss(feed, item, {
+ :places => [m.replyto],
+ :handle => nil,
+ :date => true,
+ :announce_method => :say
+ })
end
end
}
if dispItems.length > 0
+ max = @bot.config['rss.announce_max']
debug "Found #{dispItems.length} new items in #{feed}"
+ if max > 0 and dispItems.length > max
+ debug "showing only the latest #{dispItems.length}"
+ feed.watchers.each do |loc|
+ @bot.say loc, (_("feed %{feed} had %{num} updates, showing the latest %{max}") % {
+ :feed => feed.handle,
+ :num => dispItems.length,
+ :max => max
+ })
+ end
+ dispItems.slice!(max..-1)
+ end
# When displaying watched feeds, publish them from older to newer
dispItems.reverse.each { |item|
printFormattedRss(feed, item)
return seconds
end
- def printFormattedRss(feed, item, opts=nil)
+ def make_date(obj)
+ if obj.kind_of? Time
+ obj.strftime("%Y/%m/%d %H:%M")
+ else
+ obj.to_s
+ end
+ end
+
+ def printFormattedRss(feed, item, options={})
# debug item
- places = feed.watchers
- handle = feed.handle.empty? ? "" : "::#{feed.handle}:: "
- date = String.new
- if opts
- places = opts[:places] if opts.key?(:places)
- handle = opts[:handle].to_s if opts.key?(:handle)
- if opts.key?(:date) && opts[:date]
- if item.respond_to?(:updated)
- if item.updated.content.class <= Time
- date = item.updated.content.strftime("%Y/%m/%d %H:%M")
- else
- date = item.updated.content.to_s
- end
- elsif item.respond_to?(:source) and item.source.respond_to?(:updated)
- if item.source.updated.content.class <= Time
- date = item.source.updated.content.strftime("%Y/%m/%d %H:%M")
- else
- date = item.source.updated.content.to_s
- end
- elsif item.respond_to?(:pubDate)
- if item.pubDate.class <= Time
- date = item.pubDate.strftime("%Y/%m/%d %H:%M")
- else
- date = item.pubDate.to_s
- end
- elsif item.respond_to?(:date)
- if item.date.class <= Time
- date = item.date.strftime("%Y/%m/%d %H:%M")
- else
- date = item.date.to_s
- end
- else
- date = "(no date)"
- end
- date += " :: "
+ opts = {
+ :places => feed.watchers,
+ :handle => feed.handle,
+ :date => false,
+ :announce_method => @bot.config['rss.announce_method']
+ }.merge options
+
+ places = opts[:places]
+ announce_method = opts[:announce_method]
+
+ handle = opts[:handle].to_s
+
+ date = \
+ if opts[:date]
+ if item.respond_to?(:updated)
+ make_date(item.updated.content)
+ elsif item.respond_to?(:source) and item.source.respond_to?(:updated)
+ make_date(item.source.updated.content)
+ elsif item.respond_to?(:pubDate)
+ make_date(item.pubDate)
+ elsif item.respond_to?(:date)
+ make_date(item.date)
+ else
+ "(no date)"
end
+ else
+ String.new
end
tit_opt = {}
# visible in the URL anyway
# TODO make this optional?
base_title.sub!(/^Changeset \[([\da-f]{40})\]:/) { |c| "(git commit)"} if feed.type == 'trac'
- title = "#{Bold}#{base_title.ircify_html(tit_opt)}#{Bold}"
+ title = base_title.ircify_html(tit_opt)
end
desc_opt = {}
desc_opt[:limit] = @bot.config['rss.text_max']
desc_opt[:a_href] = :link_out if @bot.config['rss.show_links']
- # We prefer content_encoded here as it tends to provide more html formatting
+ # We prefer content_encoded here as it tends to provide more html formatting
# for use with ircify_html.
if item.respond_to?(:content_encoded) && item.content_encoded
desc = item.content_encoded.ircify_html(desc_opt)
link = item.link!
link.strip! if link
+ categories = item.categories!
category = item.category! || item.dc_subject!
category.strip! if category
author = item.dc_creator! || item.author!
key = @bot.global_filter_name(feed.type, @outkey)
key = @bot.global_filter_name(:default, @outkey) unless @bot.has_filter?(key)
- output = @bot.filter(key, :item => item, :handle => handle, :date => date,
- :title => title, :desc => desc, :link => link,
- :category => category, :author => author, :at => at)
+ stream_hash = {
+ :item => item,
+ :handle => handle,
+ :handle_wrap => ['::', ':: '],
+ :date => date,
+ :date_wrap => [nil, ' :: '],
+ :title => title,
+ :title_wrap => Bold,
+ :desc => desc, :link => link,
+ :categories => categories,
+ :category => category, :author => author, :at => at
+ }
+ output = @bot.filter(key, stream_hash)
return output if places.empty?
places.each { |loc|
output.to_s.each_line { |line|
- @bot.say loc, line, :overlong => :truncate
+ @bot.__send__(announce_method, loc, line, :overlong => :truncate)
}
}
end
unless errors.empty?
debug errors
self.send(:report_problem, errors.last[2], errors.last[1], m)
- return nil
+ return nil unless rss
end
items = []
if rss.nil?