Hiki Atomプラグイン
Atomプラグイン
Hikiの最近の更新をAtomフィードで配信するプラグインです。
v0.8(2010年10月25日)から、ページ毎にAtom Entryも配信できるようになりました。もしHikiをAtomPub対応にしようという心意気のある方がいれば手間を省くのにどうぞ。
Rackブランチではなく、オリジナルのHiki用です(バージョン0.8.8.1で動作確認済み)。
Rackブランチ版のプラグインはRackPluginAtomをご覧ください。このHikidashiでもこれを使っています。
ダウンロード
インストール
上のファイルをダウンロードして展開すると
- atom.rb
- en/atom.rb
- ja/atom.rb
の三ファイルが出てきます。 それぞれ
- misc/plugin
- misc/plugin/en
- misc/plugin/ja
ディレクトリーに入れてください。
プラグインを有効化すれば自動でAtomフィードが配信されます。
配信する内容(全文かダイジェストか、どんなフォーマットか)、Atom Entryを配信するかどうか、「Atom」「Atom Entry」という文字をページ内にも表示するかどうか、Atom Feedで何件の記事を配信するかは、管理画面から選択できます。
ソース
atom.rb
# = $Id: atom.rb, v1.0 2011-01-31 # Copyright (C) 2011 KITAITI Makoto <KitaitiMakoto@gmail.com> # # A Hiki plugin to syndicate Atom feed # # rss.rb plugin is very helpful for this plugin, thanks! # # == Requirements # * rss library which can make Atom feeds # (If your rss lib cannot, download from # http://www.cozmixng.org/~rwiki/?cmd=view;name=RSS+Parser) # # == Environment # Operation checked under: # * Ruby 1.8.7 # * Hiki 0.8.8.1 # # == To Do # * Refactoring(Extract Method) require 'time' def atom if @conf['atom.entry.enable'] && @page atom_entry(@page) else atom_feed end nil # Don't move to the 'FrontPage' end def atom_feed page_count = @conf['atom.count'] || atom_default_page_count pages = atom_recent_updates(page_count) last_modified = pages.first.values[0][:last_modified] header = {} if_modified_since = ENV['HTTP_IF_MODIFIED_SINCE'] if_modified_since = Time.parse(if_modified_since) if if_modified_since if if_modified_since and last_modified <= if_modified_since header['status'] = 'NOT_MODIFIED' print @cgi.header(header) else body = atom_body(pages) header['Last-Modified'] = last_modified.httpdate header['type'] = 'application/atom+xml' header['charset'] = body.encoding header['Content-Language'] = @conf.lang header['Pragma'] = 'no-cache' header['Cache-Control'] = 'no-cache' print @cgi.header(header) print euc_to_utf8(body.to_s) end end def atom_entry(page_name) page = @db.info(page_name) return print @cgi.header({'status' => 'NOT_FOUND'}) unless page return print @cgi.header({'status' => 'FORBIDDEN'}) if respond_to?(:viewable?) && !viewable?(page.keys[0].to_s) # for private-view.rb plugin last_modified = page[:last_modified] header = {} if_modified_since = ENV['HTTP_IF_MODIFIED_SINCE'] if_modified_since = Time.parse(if_modified_since) if if_modified_since if if_modified_since and last_modified <= if_modified_since header['status'] = 'NOT_MODIFIED' print @cgi.header(header) else require 'rss/maker' body = RSS::Maker.make('atom:entry') do |maker| atom_make_entry(maker, {page_name => page }, :default_author => 'anonymous', :mode => :html_full) end header['Last-Modified'] = last_modified.httpdate header['type'] = 'application/atom+xml' header['charset'] = body.encoding header['Content-Language'] = @conf.lang header['Pragma'] = 'no-cache' header['Cache-Control'] = 'no-cache' print @cgi.header(header) print euc_to_utf8(body.to_s) end end def atom_recent_updates(page_count = atom_default_page_count) pages = @db.page_info pages.reject! {|page| private_view_private_page?(page.keys[0])} if respond_to? :private_view_private_page? # for private-view.rb plugin pages.sort_by do |p| p[p.keys[0]][:last_modified] end.last(page_count).reverse end def atom_body(pages) require 'rss/maker' RSS::Maker.make('atom') do |maker| maker.encoding = 'UTF-8' maker.channel.author = @conf.author_name maker.channel.about = @conf.index_url + '?c=atom' maker.channel.title = @conf.site_name + ' : ' + label_atom_recent maker.channel.description = @conf.site_name + ' ' + label_atom_recent maker.channel.language = @conf.lang maker.channel.date = pages.first.values[0][:last_modified] maker.channel.rights = 'Copyright (C) ' + @conf.author_name maker.channel.generator do |generator| generator.uri = 'http://hikiwiki.org/' generator.version = ::Hiki::VERSION generator.content = 'Hiki' end maker.channel.links.new_link do |link| link.rel = 'self' link.type = 'application/atom+xml' link.href = maker.channel.about end maker.channel.links.new_link do |link| link.rel = 'alternate' link.type = 'text/html' link.href = @conf.index_url + '?c=recent' end pages.each do |page| atom_make_entry(maker, page) end end end def atom_make_entry(maker, page, options = {}) maker.items.new_item do |item| name = page.keys[0] uri = @conf.index_url + '?' + name.escape item.title = page_name(name) item.link = uri item.author = options[:default_author] if options[:default_author] item.author = page[name][:editor] if page[name][:editor] item.date = page[name][:last_modified].utc.strftime('%Y-%m-%dT%H:%M:%S+00:00') item.content.type = 'html' item.content.content = atom_make_content(page, options[:mode]) end end def atom_make_content(page, mode = nil) mode ||= @conf['atom.mode'] raise ArgumentError, "Unknown mode: #{mode}" unless [:unidiff, :worddiff_digest, :worddiff_full, :html_full].include?(mode) name = page.keys[0] src = @db.load_backup(name) || '' dst = @db.load(name) || '' case mode when :unidiff content = h(unified_diff(src, dst)).strip.gsub(/\n/, "<br>\n").gsub(/ /, ' ') when :worddiff_digest content = word_diff(src, dst, true).strip.gsub(/\n/, "<br>\n") when :worddiff_full content = word_diff(src, dst).strip.gsub(/\n/, "<br>\n") when :html_full tokens = @db.load_cache(name) unless tokens parser = @conf.parser.new(@conf) tokens = parser.parse(@db.load(name)) @db.save_cache(name, tokens) end tmp = @conf.use_plugin begin @conf.use_plugin = false formatter = @conf.formatter.new(tokens, @db, Plugin.new(@conf.options, @conf), @conf) content = formatter.to_s ensure @conf.use_plugin = tmp end end if content.empty? content = shorten(dst).strip.gsub(/\n/, "<br>\n") end content end def atom_max_page_count; 50; end def atom_default_page_count; 10; end add_body_enter_proc do @conf['atom.mode'] ||= :unidiff @conf['atom.menu'] ? add_plugin_command('atom', 'Atom') : add_plugin_command('atom', nil) end add_header_proc do %Q| <link rel="alternate" type="application/atom+xml" title="Atom" href="#{@conf.index_url}?c=atom">\n| end if @conf['atom.entry.enable'] if @conf['atom.entry.menu-display'] add_menu_proc {%Q|<a href="#{@conf.index_url}?c=atom;p=#{@page.escape}">Atom Entry</a>|} if @page end add_header_proc { %Q| <link rel="alternate" type="application/atom+xml" title="Atom Entry" href="#{@conf.index_url}?c=atom;p=#{@page.escape}">| } if @page end def atom_saveconf if @mode == 'saveconf' @conf['atom.mode'] = @cgi.params['atom.mode'][0].intern @conf['atom.count'] = [@cgi.params['atom.count'][0].to_i, atom_max_page_count].min @conf['atom.count'] = [@conf['atom.count'], 1].max end end if @cgi.params['conf'][0] == 'atom' && @mode == 'saveconf' @conf['atom.menu'] = (@cgi.params['atom.menu'][0] == 'true') @conf['atom.entry.enable'] = (@cgi.params['atom.entry.enable'][0] == 'true') @conf['atom.entry.menu-display'] = (@cgi.params['atom.entry.menu-display'][0] == 'true') end add_conf_proc('atom', label_atom_config) do atom_saveconf str = <<HTML <h3 class="subtitle">#{label_atom_mode_title}</h3> <p><select name="atom.mode"> HTML label_atom_mode_candidate.each_pair do |value, label| str << %Q|<option value="#{value}"#{' selected' if @conf['atom.mode'] == value}>#{label}</option>\n| end str << "</select></p>\n" str << <<HTML <h3 class="subtitle">#{label_atom_menu_title}</h3> <p><label><input type="radio" name="atom.menu" value="true" #{'checked="checked"' if @conf['atom.menu']}> #{label_atom_menu_enable}</label> <label><input type="radio" name="atom.menu" value="false" #{'checked="checked"' unless @conf['atom.menu']}> #{label_atom_menu_disable}</label>\n HTML str << <<HTML <h3 class="subtitle">#{label_atom_count_title}</h3> <p><input name="atom.count" size="4" value="#{@conf['atom.count']}"> #{label_atom_count_unit}(#{label_atom_max_page_count})</p> HTML str << <<HTML <h3 class="subtitle">#{label_atom_entry_enable_title}</h3> <p><label><input type="radio" name="atom.entry.enable" value="true" #{'checked="checked"' if @conf['atom.entry.enable']}> #{label_atom_entry_enable}</label> <label><input type="radio" name="atom.entry.enable" value="false" #{'checked="checked"' unless @conf['atom.entry.enable']}> #{label_atom_entry_disable}</label></p> <h3 class="subtitle">#{label_atom_entry_menu_display_title}</h3> <p><label><input type="radio" name="atom.entry.menu-display" value="true" #{'checked="checked"' if @conf['atom.entry.menu-display']}> #{label_atom_entry_menu_display}</label> <label><input type="radio" name="atom.entry.menu-display" value="false" #{'checked="checked"' unless @conf['atom.entry.menu-display']}> #{label_atom_entry_menu_hide}</label></p> HTML end export_plugin_methods :atom
en/atom.rb
# en/atom.rb def label_atom_recent 'Recent Changes' end def label_atom_config; 'Atom syndication'; end def label_atom_mode_title; 'Select the format of the feed.' end def label_atom_mode_candidate { :unidiff => 'unified diff', :worddiff_digest => 'word diff(digest)', :worddiff_full => 'word diff(full text)', :html_full => 'HTML(full text)', } end def label_atom_menu_title; 'Add Atom Feed menu'; end def label_atom_menu_candidate [ 'Yes', 'No' ] end def label_atom_count_title; 'The count of syndicated feeds'; end def label_atom_count_unit; 'pages'; end def label_atom_max_page_count; "#{atom_max_page_count} at a max"; end def label_atom_entry_enable_title; 'Syndicate Atom Entry per page'; end def label_atom_entry_enable; 'Yes'; end def label_atom_entry_disable; 'No'; end def label_atom_entry_menu_display_title; 'Add Atom Entry menu'; end def label_atom_entry_menu_display; 'Yes'; end def label_atom_entry_menu_hide; 'No'; end
ja/atom.rb
# -*- coding: euc-jp -*- # ja/atom.rb def label_atom_recent '更新日時順' end def label_atom_config; 'Atom の配信'; end def label_atom_mode_title; 'Atom Feedのフォーマット'; end def label_atom_mode_candidate { :unidiff => 'unified diff 形式', :worddiff_digest => 'word diff 形式 (ダイジェスト)', :worddiff_full => 'word diff 形式 (全文)', :html_full => 'HTML 形式 (全文)', } end def label_atom_menu_title; 'Atom Feedメニューの表示'; end def label_atom_menu_candidate [ 'する', 'しない' ] end def label_atom_count_title; 'Atom Feedで配信するページの数'; end def label_atom_count_unit; '件'; end def label_atom_max_page_count; "最大#{atom_max_page_count}件"; end def label_atom_entry_enable_title; '各ページのAtom Entryの配信'; end def label_atom_entry_enable; 'する'; end def label_atom_entry_disable; 'しない'; end def label_atom_entry_menu_display_title; '各ページのAtom Entryメニューの表示'; end def label_atom_entry_menu_display; 'する'; end def label_atom_entry_menu_hide; 'しない'; end
メモ
- エントリーごとにキャッシュを作って、古いページのキャッシュと新しいページから作ったエントリーとを組み合わせてフィードを構築するとちょっとパフォーマンスがいいかも知れない。
- Ruby標準添付のrssライブラリーを使っているが、特定のエントリーだけ構築済みのXML要素を使うってできるんだろうか。
- そう言えば、例えばrss-showプラグインなんかを使って動的にページを作るやつのキャッシュは考えなきゃいけない、よくある問題だけど。Ajaxで読み込んじゃうのがいいと思うんだよな。
- Atom Entry配信の際、毎回info.dbにアクセスしなければならないのが気に食わないが、HTTPのIf-Modified-Since付きのリクエストに対応する為には仕方が無い。大域幅を節約するかサーバー負荷を減らすかっていう問題だが。
- キャッシュファイルを作るんだったら、それを読み込んで解析してしまえばそのページのLast-Modifiedは出るから、info.dbに触らないでもIf-Modified-Sinceに対応できるかも知れない。その場合は勿論Atom Entryを解析する負荷がサーバーに掛かるが、とても壊れやすいinfo.dbに触るよりはましなんじゃないかという気がする。それに、Last-Modifiedとentry要素のペアでキャッシュしてもいいしね。
- キーワード毎の最新記事を配信できるようにしたい。
Referer | 282 | 280 | 113 | 38 | 15 | 13 | 13 | 10 | 10 | 9 | 7 | 7 | 6 | 6 | 6 | 6 | 5 | 5 | 5 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
Keyword(s):
References:[自作プラグイン]