http://www.largevocalmix.jp/diary/2008/02/10
『栞と紙魚子の怪奇事件簿』を消化。前田敦子の制服姿はかわいくないので先週みたいにニットのセーターにして頂きたい。
りりあんのブログに未来日付の記事があるのでそれを拾わないようにした。他に '\0' が置換されることがあったんだけど面倒だから見なかったことにする。
#!/home/_/opt/ruby/bin/ruby -Ku
#------------------------------------------------------------
# 巡回用アンテナ
# ・仕様
# ・失敗したら更新時刻を平成元年としてソート順の最後にする
# ・RSSは実行時の日付を含む過去の記事を取得する
# ・メモ
# ・コーディング規約
# http://www.loveruby.net/w/RubyCodingStyle.html
# ・Time.getlocal.w3cdtf
# require 'rss' -> alias iso8601 -> alias xmlschema -> 文字列
# ・繰り返しの制御
# break : 繰り返しを中断し、繰り返しの中から抜ける
#------------------------------------------------------------
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)
USER_AGENT = '_'
#------------------------------------------------------------
# クラスの定義
# ・クラス名は大文字で始める
# ・インスタンス変数は '@' で始まる
# ・読み書き可能なインスタンス変数は attr_accessor で定義できる
# ・Class.new で initialize が呼び出される
# ・オブジェクトにとって必要な初期化処理は initialize に記述する
#------------------------------------------------------------
class Utils
def self.kako?(date)
date.strftime(COMPARE_DATE_FORMAT) <= CURRENT_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)
if not in_feed.empty?
@feed = URI(in_feed)
else
@feed = nil
end
@update_date = HEISEI_GANNEN
@preview = '初期値'
end
def to_s
debug = ''
debug += "[title]#{@title}" + "\n"
debug += "[uri]#{@uri.to_s}" + "\n"
debug += "[feed]#{@feed.to_s}" + "\n"
debug += "[update_date]#{@update_date}" + "\n"
debug += "[preview]#{@preview}"
end
def format_date
@update_date.sub(/T/, ' ').sub(/\+09:00/, '')
end
def <=> (other)
@update_date <=> other.update_date
end
end
class LastModified < Antenna
def to_html
%(<li>[#{format_date}] <a href="#{@uri.to_s}">#{CGI.escapeHTML(@title)}</a><div class="preview">not feed</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
end
end
end
class Feed < Antenna
def to_html
if @preview.empty?
%(<li>[#{format_date}] <a href="#{@uri.to_s}">#{CGI.escapeHTML(@title)}</a><div class="preview">empty feed</div></li>)
else
%(<li>[#{format_date}] <a href="#{@uri.to_s}">#{CGI.escapeHTML(@title)}</a><div class="preview">#{CGI.escapeHTML(@preview)}</div></li>)
end
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
@preview = item.title.chomp
break
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
@preview = item.title.chomp
break
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
@preview = item.title.chomp
break
end
end
end
end
else
#HTTPステータス異常
@preview = 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 = ex.class.name
rescue Timeout::Error
site.preview = 'Timeout::Error'
end
end
write_antenna(site_list.sort.reverse)