SlashModel::ELEMENTS.collect! {|name| "#{SLASH_PREFIX}_#{name}"}
end
+ if self.const_defined? :Atom
+ # There are improper Atom feeds around that use the non-standard
+ # 'modified' element instead of the correct 'updated' one. Let's
+ # support it too.
+ module Atom
+ class Feed
+ class Modified < RSS::Element
+ include CommonModel
+ include DateConstruct
+ end
+ __send__("install_have_child_element",
+ "modified", URI, nil, "modified", :content)
+
+ class Entry
+ Modified = Feed::Modified
+ __send__("install_have_child_element",
+ "modified", URI, nil, "modified", :content)
+ end
+ end
+ end
+ end
+
class Element
class << self
def def_bang(name, chain)
end
end
+ # Atom categories are squashed to their label only
{
:link => %w{link.href link},
:guid => %w{guid.content guid},
:content => %w{content.content content},
:description => %w{description.content description},
:title => %w{title.content title},
- :category => %w{category.content category},
+ :category => %w{category.content category.label category},
:dc_subject => %w{dc_subject},
:author => %w{author.name.content author.name author},
: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.label rescue c rescue nil }
+ end.compact
+ cats.empty? ? nil : cats
+ end
+
protected
def blank2nil(&block)
x = yield
: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,
+ # Currently only supported is bot.config['rss.show_updated']: when false,
# only the guid/link is accounted for.
def make_uid(item)
# 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 unless ss[nk].nil?
+ 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
# 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 under botclass.
+ # 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'
+ # 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
+ #
def define_filters
@outkey ||= :"rss.out"
end
case params[:what].intern
when :handle
- new = params[:new].downcase
- if @feeds.key?(new) and @feeds[new]
+ # preserve rename case, but beware of key
+ realnew = params[:new]
+ new = realnew.downcase
+ if feed.handle.downcase == new
+ if feed.handle == realnew
+ m.reply _("You want me to rename %{handle} to itself?") % {
+ :handle => feed.handle
+ }
+ return false
+ else
+ feed.mutex.synchronize do
+ feed.handle = realnew
+ end
+ end
+ elsif @feeds.key?(new) and @feeds[new]
m.reply "There already is a feed with handle #{new}"
return
else
feed.mutex.synchronize do
@feeds[new] = feed
@feeds.delete(handle)
- feed.handle = new
+ feed.handle = realnew
end
handle = new
end
stop_watches
# Read watches from list.
- watchlist.each{ |handle, feed|
- watchRss(feed, m)
+ watchlist.each{ |hndl, fd|
+ watchRss(fd, m)
}
m.okay if m
end
debug "fetching #{feed}"
first_run = !feed.last_success
- if (@bot.config['rss.announce_timeout'] > 0 &&
+ if (!first_run && @bot.config['rss.announce_timeout'] > 0 &&
(Time.now - feed.last_success > @bot.config['rss.announce_timeout']))
debug "#{feed} wasn't polled for too long, supressing output"
first_run = true
# debug item
opts = {
:places => feed.watchers,
- :handle => feed.handle.empty? ? "" : "::#{feed.handle}:: ",
+ :handle => feed.handle,
:date => false,
:announce_method => @bot.config['rss.announce_method']
}.merge options
- date = String.new
-
places = opts[:places]
- handle = opts[:handle].to_s
announce_method = opts[:announce_method]
+ handle = opts[:handle].to_s
+
+ date = \
if opts[:date]
- if item.respond_to?(:updated)
- date = make_date(item.updated.content)
+ if item.respond_to?(:updated) and item.updated
+ make_date(item.updated.content)
+ elsif item.respond_to?(:modified) and item.modified
+ make_date(item.modified.content)
elsif item.respond_to?(:source) and item.source.respond_to?(:updated)
- date = make_date(item.source.updated.content)
+ make_date(item.source.updated.content)
elsif item.respond_to?(:pubDate)
- date = make_date(item.pubDate)
+ make_date(item.pubDate)
elsif item.respond_to?(:date)
- date = make_date(item.date)
+ make_date(item.date)
else
- date = "(no date)"
+ "(no date)"
end
- date << " :: "
+ 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 = {}
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?