http://www.largevocalmix.jp/diary/2008/02/11
ブログの記事別に更新時刻を確認できるようにした。しばらく動かして問題なければ念願の「しょこたん☆ぶろぐ」の取り込みですよ、奥さん。
#!/home/_/opt/ruby/bin/ruby -Ku
#------------------------------------------------------------
# 巡回用アンテナ
# ・仕様
# ・失敗したら更新時刻を平成元年としてソート順の最後にする
# ・RSSは実行時の日付を含む過去の記事を取得する
# ・メモ
# ・コーディング規約
# http://www.loveruby.net/w/RubyCodingStyle.html
# ・Time.getlocal.w3cdtf
# require 'rss' -> alias iso8601 -> alias xmlschema -> 文字列
# ・繰り返しの制御
# break : 繰り返しを中断し、繰り返しの中から抜ける
# ・文字列
# ・「""」を使えば「\」を使った特殊文字を表現することができる
# ・%記法「%()」を使えばダブルクォート文字列を表現することができる
# http://www.ruby-lang.org/ja/man/html/_A5EAA5C6A5E9A5EB.html#a.25.b5.ad.cb.a1
#------------------------------------------------------------
require 'rexml/document'
require 'net/http'
require 'time'
require 'rss'
require 'cgi'
#------------------------------------------------------------
# 定数の定義
# ・定数はアルファベットの大文字で始まる
#------------------------------------------------------------
APPLICATION_PATH = '/home/_/app/'
PUBLIC_PATH = '/home/_/www/meegle/'
SITE_LIST_XML_PATH = APPLICATION_PATH + '_'
ANTENNA_TEMPLATE_PATH = APPLICATION_PATH + '_'
ANTENNA_HTML_PATH = PUBLIC_PATH + '_'
CHECK_LAST_MODIFIED = 'LastModified'.downcase
CHECK_FEED = 'Feed'.downcase
RSS10 = 'RSS::RDF'.downcase
RSS20 = 'RSS::Rss'.downcase
HEISEI_GANNEN = '1989-01-08T00:00:00+09:00'
COMPARE_DATE_FORMAT = '%Y%m%d'
CURRENT_DATE = Time.now.getlocal.strftime(COMPARE_DATE_FORMAT)
MAX_ARTICLE_COUNT = 7
USER_AGENT = '_'
#------------------------------------------------------------
# クラスの定義
# ・クラス名は大文字で始める
# ・インスタンス変数は '@' で始まる
# ・読み書き可能なインスタンス変数は attr_accessor で定義できる
# ・Class.new で initialize が呼び出される
# ・オブジェクトにとって必要な初期化処理は initialize に記述する
# ・super でスーパークラスの同名メソッドを呼ぶ
#------------------------------------------------------------
class Utils
def self.kako?(date)
date.strftime(COMPARE_DATE_FORMAT) <= CURRENT_DATE
end
def self.trim_date(date)
date.sub(/T/, ' ').sub(/\+09:00/, '')
end
end
class Article
attr_accessor :update_date, :title, :link
def initialize
@update_date = HEISEI_GANNEN
@title = ''
@link = ''
end
def format_date
Utils.trim_date(@update_date)
end
end
class Antenna
attr_accessor :title, :uri, :feed, :update_date, :preview
def initialize(in_title, in_uri, in_feed='')
@title = in_title
@uri = URI(in_uri)
unless in_feed.empty?
@feed = URI(in_feed)
else
@feed = nil
end
@update_date = HEISEI_GANNEN
@preview = []
end
def format_date
Utils.trim_date(@update_date)
end
def <=> (other)
@update_date <=> other.update_date
end
end
class LastModified < Antenna
def preview_error(cause)
@preview.push(cause)
end
def to_html
%(<li>[#{format_date}] <a href="#{@uri.to_s}">#{@title}</a><div class="preview">#{@preview[0]}</div></li>)
end
def check
req = Net::HTTP::Head.new(@uri.path)
req['User-Agent'] = USER_AGENT
res = Net::HTTP.start(@uri.host, @uri.port) do |http|
http.request(req)
end
case res
when Net::HTTPSuccess
@update_date = Time.parse(res['Last-Modified']).getlocal.w3cdtf
@preview.push(res.class.name)
else
#HTTPステータス異常
preview_error(res.class.name)
end
end
end
class Feed < Antenna
attr_accessor :error
def initialize(in_title, in_uri, in_feed)
super(in_title, in_uri, in_feed)
@error = false
end
def preview_error(cause)
article = Article.new
article.title = cause
@preview.push(article)
@error = true
end
def preview_html
html = '<ul class="preview">'
unless error
@preview.each_with_index do |article, i|
if i % 2 == 0
html += %(<li class="alternate">[#{article.format_date}] <a href="#{article.link}">#{article.title}</a></li>)
else
html += %(<li>[#{article.format_date}] <a href="#{article.link}">#{article.title}</a></li>)
end
end
else
html += %(<li>[#{@preview[0].format_date}] #{@preview[0].title}</li>)
end
html += '</ul>'
end
def to_html
%(<li>[#{format_date}] <a href="#{@uri.to_s}">#{@title}</a><div class="preview">#{preview_html}</div></li>)
end
# jugem.jp/?mode=rss用
def path
if @feed.query == nil
@feed.path
else
"#{@feed.path}?#{@feed.query}"
end
end
def check
req = Net::HTTP::Get.new(path)
req['User-Agent'] = USER_AGENT
res = Net::HTTP.start(@feed.host, @feed.port) do |http|
http.request(req)
end
case res
when Net::HTTPSuccess
rss = RSS::Parser.parse(res.body)
case rss.class.name.downcase
when RSS10
rss.items.sort_by{|a| a.dc_date}.reverse.each do |item|
if Utils.kako?(item.dc_date.getlocal)
@update_date = item.dc_date.getlocal.w3cdtf if @preview.empty?
article = Article.new
article.update_date = item.dc_date.getlocal.w3cdtf
article.title = item.title.chomp
article.link = item.link
@preview.push(article)
break if @preview.size == MAX_ARTICLE_COUNT
end
end
when RSS20
#RSS 2.0でもDublin Coreモジュールを使う場合がある
if rss.channel.items.first.dc_date
rss.channel.items.sort_by{|a| a.dc_date}.reverse.each do |item|
if Utils.kako?(item.dc_date.getlocal)
@update_date = item.dc_date.getlocal.w3cdtf if @preview.empty?
article = Article.new
article.update_date = item.dc_date.getlocal.w3cdtf
article.title = item.title.chomp
article.link = item.link
@preview.push(article)
break if @preview.size == MAX_ARTICLE_COUNT
end
end
else
rss.channel.items.sort_by{|a| a.pubDate}.reverse.each do |item|
if Utils.kako?(item.pubDate.getlocal)
@update_date = item.pubDate.getlocal.w3cdtf if @preview.empty?
article = Article.new
article.update_date = item.pubDate.getlocal.w3cdtf
article.title = item.title.chomp
article.link = item.link
@preview.push(article)
break if @preview.size == MAX_ARTICLE_COUNT
end
end
end
end
else
#HTTPステータス異常
preview_error(res.class.name)
end
end
end
#------------------------------------------------------------
# メソッドの定義
# ・戻り値は最後に評価した式になる
# ・return で直接戻り値を返せる
#------------------------------------------------------------
def read_site_list
site_list = Array.new
xml = REXML::Document.new(File.new(SITE_LIST_XML_PATH))
xml.elements.each('sitelist/site') do |site|
info = site.elements
case info['type'].text.downcase
when CHECK_LAST_MODIFIED
site_list.push(LastModified.new(info['title'].text, info['uri'].text))
when CHECK_FEED
site_list.push(Feed.new(info['title'].text, info['uri'].text, info['feed'].text))
end
end
return site_list
end
def write_antenna(site_list)
template = File.read(ANTENNA_TEMPLATE_PATH)
antenna = ''
indent = ' ' * 8
site_list.each do |site|
antenna += indent + site.to_html + "\n"
end
template.sub!(/antenna_body/, antenna)
template.sub!(/ruby_version/, RUBY_VERSION)
template.sub!(/rss_parser_version/, RSS::VERSION)
File.open(ANTENNA_HTML_PATH, 'w') do |writer|
writer.puts(template)
end
File.chmod(0604, ANTENNA_HTML_PATH)
end
#------------------------------------------------------------
# 処理の開始
#------------------------------------------------------------
site_list = read_site_list
site_list.each do |site|
begin
site.check
rescue => ex
site.preview_error(ex.class.name)
rescue Timeout::Error
site.preview_error('Timeout::Error')
end
end
write_antenna(site_list.sort.reverse)