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)