class RSS2Mail::Feed

Constants

KEEP

Attributes

content[R]
debug[R]
feed[R]
reload[R]
rss[R]
simple[R]
transport[R]
updated[R]
verbose[R]

Public Class Methods

new(feed, options = {}) click to toggle source
# File lib/rss2mail/feed.rb, line 35
def initialize(feed, options = {})
  raise TypeError, "Hash expected, got #{feed.class}" unless feed.is_a?(Hash)

  required = [:url, :to, :title].delete_if { |key| feed.key?(key) }

  unless required.empty?
    raise ArgumentError, "Feed incomplete: #{required.join(', ')} missing"
  end

  @feed    = feed
  @simple  = feed[:simple]
  @updated = feed[:updated]

  @reload  = options[:reload]
  @verbose = options[:verbose]
  @debug   = options[:debug]

  klass, opt = transport_from(options)
  @transport = "#{klass.name.split('::').last} @ #{opt}"

  extend klass
end

Public Instance Methods

deliver(templates) click to toggle source
# File lib/rss2mail/feed.rb, line 62
def deliver(templates)
  check_deliver_requirements if respond_to?(:check_deliver_requirements)

  if (to = Array(feed[:to])).empty?
    log 'No one to send to'
    return
  end

  unless get && parse
    log 'Nothing to send'
    return
  end

  if (items = rss.items).empty?
    log 'No new items'
    return
  end

  type = feed[:content_type] || 'text/html'

  if template = templates[type[/\/(.*)/, 1]]
    type = "Content-type: #{type}; charset=#{feed[:encoding] || 'UTF-8'}"
  else
    log "Template not found: #{type}"
    return
  end

  sent = feed[:sent] ||= []
  count, title = 0, feed[:title]

  items.each { |item|
    link, subject, body = render(feed, item, template)

    send_mail(to, "[#{title}] #{subject}", body, type) {
      sent << link
      count += 1
    } if link
  }

  sent.uniq!
  sent.slice!(0...-KEEP)

  log "#{count} items sent"
  count
end

Private Instance Methods

error(err = nil, occasion = nil, extra = nil) click to toggle source
# File lib/rss2mail/feed.rb, line 239
def error(err = nil, occasion = nil, extra = nil)
  msg = 'Error'

  msg << " #{occasion}"            if occasion
  msg << ": #{err} (#{err.class})" if err
  msg << " [#{extra}]"             if extra

  msg = [msg, *err.backtrace].join("\n    ") if debug

  log msg, true
end
get(reload = reload()) click to toggle source
# File lib/rss2mail/feed.rb, line 133
def get(reload = reload())
  conditions = {}

  if reload
    @content = nil
  else
    if etag = feed[:etag]
      conditions['If-None-Match'] = etag
    end

    if mtime = feed[:mtime]
      conditions['If-Modified-Since'] = mtime
    end
  end

  log conditions.inspect, debug

  begin
    open_feed(feed[:url], conditions) { |uri|
      if etag = uri.meta['etag']
        feed[:etag] = etag
      else
        feed.delete(:etag)
      end

      if mtime = begin; uri.last_modified; rescue ArgumentError; end
        feed[:mtime] = mtime.rfc822
      else
        feed.delete(:mtime)
      end

      unless etag || mtime
        feed[:updated] = Time.now
      else
        feed.delete(:updated)
      end

      @content ||= uri.read
    }

    log feed.values_at(:etag, :mtime, :updated).inspect, debug
  rescue OpenURI::HTTPError => err
    log "Feed not found or unchanged: #{err} (#{err.class})"
  rescue Exception => err
    error err, 'while getting feed'
  end

  content
end
log(msg, verbose = verbose()) click to toggle source
# File lib/rss2mail/feed.rb, line 235
def log(msg, verbose = verbose())
  warn "[#{feed[:title]}] #{msg}" if verbose
end
parse(reload = reload()) click to toggle source
# File lib/rss2mail/feed.rb, line 183
def parse(reload = reload())
  @rss = nil if reload

  if content
    @rss ||= RSS.feed(content, simple) { |err|
      error err, 'while parsing feed'
    }

    if rss && !reload
      if update = updated
        rss.items.delete_if { |item| (date = item.date) && date <= update }
      end

      if sent = feed[:sent]
        set = Set.new(sent)
        rss.items.delete_if { |item| set.include?(item.link) }
      end
    end
  else
    log 'Nothing to parse'
  end

  rss
end
render(feed, item, template) click to toggle source
# File lib/rss2mail/feed.rb, line 208
def render(feed, item, template)
  title       = item.title
  link        = item.link
  description = item.description(feed[:unescape_html])
  date        = item.date
  author      = item.author
  body        = item.body(feed[:body])
  subject     = item.subject

  log "#{title} / #{date} [#{author}]", debug
  log "<#{link}>", debug

  [link, subject, ERB.new(template).result(binding)]
rescue => err
  error err, 'while rendering feed', title
end
send_mail(*args) { || ... } click to toggle source
# File lib/rss2mail/feed.rb, line 225
def send_mail(*args)
  return if debug

  deliver_mail(*args)

  yield if block_given?
rescue Exception => err
  error err, 'while sending mail', transport
end
transport_from(options) click to toggle source
# File lib/rss2mail/feed.rb, line 110
def transport_from(options)
  if lmtp = options[:lmtp] or smtp = options[:smtp]
    klass = smtp ? Transport::SMTP : Transport::LMTP

    case @smtp = lmtp || smtp
      when Array  # ok
      when true   then @smtp = []
      when Fixnum then @smtp = [nil, @smtp]
      when String then @smtp = @smtp.split(':')
      else raise TypeError, "Array expected, got #{@smtp.class}"
    end

    host, port = @smtp.shift, @smtp.shift

    host = klass::DEFAULT_HOST if host.nil? || host.empty?
    port = klass::DEFAULT_PORT if port.nil?

    [klass, @smtp.unshift(port.to_i).unshift(host)[0, 4].join(':')]
  else
    [klass = Transport::Mail, "#{klass::CMD} = #{klass::BIN.inspect}"]
  end
end