RSSフィードを集約したフィードを生成するWebアプリをSinatraで作ってみた

久し振りのプログラミング記事更新。
ボカロ関連のニュース情報を集めたフィードが欲しいと思い、RSSフィードを集約したフィードを生成するWebアプリをSinatraで作ってみた。
完成形は、下記のURLより参照できる。
Vocafeeds - ボカロ関連ニュース集約フィード

RSSの解析と生成には、標準のrssライブラリを使用。
GoogleアラートのフィードがATOM形式でrssライブラリでは解析できないので、REXMLライブラリで解析。

コードは下記のようになった(一部抜粋)。
hatenaメソッドがRSSの解析、add_itemメソッドがATOMの解析に相当。

require 'rubygems'
require 'sinatra'
require 'date'
require 'open-uri'
require 'rss'
require 'rexml/document'

configure do
  HATENA_URL = 'http://b.hatena.ne.jp/t/VOCALOID?sort=hot&threshold=&mode=rss'
  VOCALOID_GUIDE_URL = 'http://vocaloguide.com/feed'
  GOOGLE_ALERT_NEWS_VOCALOID_URL = 'http://www.google.com/alerts/feeds/11697367392137858374/16433312303441767819'
  GOOGLE_ALERT_NEWS_CHARACTER_URL = 'http://www.google.com/alerts/feeds/11697367392137858374/4844081763401052883'
  GOOGLE_ALERT_VOCALOID_URL = 'http://www.google.com/alerts/feeds/11697367392137858374/12241483289961948968'
  GOOGLE_ALERT_VOCALOID_ENGLISH_URL = 'http://www.google.com/alerts/feeds/11697367392137858374/7009353441646457352'
end

get '/feeds' do
  output = RSS::Maker.make("1.0") do |maker|
    maker.channel.about = "http://vocafeeds.champl.org/feeds"
    maker.channel.title = "Vocafeeds ボカロ関連ニュース集約フィード"
    maker.channel.description = "ボカロ関連ニュースのフィードを集約したフィード。"
    maker.channel.link = "http://vocafeeds.champl.org/"
    maker.items.do_sort = true

    hatena(maker)
    add_item(GOOGLE_ALERT_NEWS_VOCALOID_URL, maker)
    add_item(GOOGLE_ALERT_NEWS_CHARACTER_URL, maker)
    add_item(GOOGLE_ALERT_VOCALOID_URL, maker)
    add_item(GOOGLE_ALERT_VOCALOID_ENGLISH_URL, maker)
    add_item(VOCALOID_GUIDE_URL, maker)
  end
  content_type "application/xml"
  output.to_s  
end

def hatena(maker)  
  open HATENA_URL do |http|
    hatena = RSS::Parser.parse(http.read)
    hatena.items.each do |entry|
      next if entry.link =~ %r{http://www.nicovideo.jp/watch/}
      
      maker.items.new_item do |item|
        item.link = entry.link
        item.title = entry.title
        item.description = entry.description
        item.date = entry.date
      end        
    end
  end
end

def make_rss(about, title, description, link, &block)
  output = RSS::Maker.make("1.0") do |maker|
    maker.channel.about = about
    maker.channel.title = title
    maker.channel.description = description
    maker.channel.link = link
    maker.items.do_sort = true

    block.call(maker)

    if maker.items.empty? then
      logger.info "empty!"
      maker.items.new_item do |item|
        item.link = "http://vocafeeds.champl.org/"
        item.title = "(no entry)"
        item.description = "エントリがありません"
        item.date = Time.now
      end
    end
  end
  content_type "application/xml"
  output.to_s
end

def add_item(url, maker)
  open url do |http|
    doc = REXML::Document.new(http.read)
    REXML::XPath.match(doc,"feed/entry").each do |entry|
      maker.items.new_item do |item|
        item.link = REXML::XPath.first(entry, "link").attributes["href"]
        item.title = REXML::XPath.first(entry, "title").text
        item.description = REXML::XPath.first(entry, "content").text
        item.date = REXML::XPath.first(entry, "updated").text
      end        
    end
  end
end