| 1 | # show my hot-entry in Hatena::Bookmark |
|---|
| 2 | # |
|---|
| 3 | # usage: |
|---|
| 4 | # <%= my_hotentry %> |
|---|
| 5 | # |
|---|
| 6 | # Copyright (c) MATSUOKA Kohei <http://www.machu.jp/> |
|---|
| 7 | # Distributed under the GPL |
|---|
| 8 | # |
|---|
| 9 | require 'uri' |
|---|
| 10 | require 'open-uri' |
|---|
| 11 | require 'rexml/document' |
|---|
| 12 | require 'pstore' |
|---|
| 13 | require 'timeout' |
|---|
| 14 | |
|---|
| 15 | # 人気の日記のソート順(新着順: eid, 注目順: hot, 人気順: count) |
|---|
| 16 | @conf ||= {} |
|---|
| 17 | @conf['my_hotentry.sort'] ||= 'hot' |
|---|
| 18 | |
|---|
| 19 | class MyHotEntry |
|---|
| 20 | def initialize(dbfile) |
|---|
| 21 | @dbfile = dbfile |
|---|
| 22 | end |
|---|
| 23 | |
|---|
| 24 | # 人気の日記の一覧を返す |
|---|
| 25 | def entries |
|---|
| 26 | r = nil |
|---|
| 27 | PStore.new(@dbfile).transaction(true) do |db| |
|---|
| 28 | r = db[:entries] |
|---|
| 29 | end |
|---|
| 30 | r || [] |
|---|
| 31 | end |
|---|
| 32 | |
|---|
| 33 | # 人気の日記一覧を取得する |
|---|
| 34 | def update(base_url, options = {}) |
|---|
| 35 | options[:title] ||= '' |
|---|
| 36 | options[:sort] ||= 'eid' |
|---|
| 37 | options[:threshold] ||= 3 |
|---|
| 38 | |
|---|
| 39 | # RSSを取得 |
|---|
| 40 | rss = nil |
|---|
| 41 | rss_url = 'http://b.hatena.ne.jp/entrylist?mode=rss&url=' |
|---|
| 42 | rss_url << URI.escape(base_url, /[^-.!~*'()\w]/n) |
|---|
| 43 | rss_url << "&sort=#{options[:sort]}&threshold=#{options[:threshold]}" |
|---|
| 44 | begin |
|---|
| 45 | timeout(5) do |
|---|
| 46 | # convert Tempfile to String because REXML can't accept Tempfile |
|---|
| 47 | open(rss_url) do |f| |
|---|
| 48 | rss = REXML::Document.new(f.readlines.join("\n")) |
|---|
| 49 | end |
|---|
| 50 | end |
|---|
| 51 | rescue TimeoutError => e |
|---|
| 52 | return |
|---|
| 53 | end |
|---|
| 54 | # RDF/itemが空ならDBを更新しない (たまにitemが空のデータが返るため) |
|---|
| 55 | return if rss.elements['rdf:RDF/item'].nil? |
|---|
| 56 | |
|---|
| 57 | # キャッシュに格納する |
|---|
| 58 | PStore.new(@dbfile).transaction do |db| |
|---|
| 59 | db[:entries] = [] |
|---|
| 60 | rss.elements.each('rdf:RDF/item') do |item| |
|---|
| 61 | url = item.elements['link'].text |
|---|
| 62 | title = item.elements['title'].text |
|---|
| 63 | # リンク先のタイトルからサイト名と日付を取り除く |
|---|
| 64 | title.sub!(/( - )?#{options[:html_title]}( - )?/, '') |
|---|
| 65 | title.sub!(/\(\d{4}-\d{2}-\d{2}\)/, '') |
|---|
| 66 | db[:entries].push({ :url => url, :title => title }) |
|---|
| 67 | end |
|---|
| 68 | end |
|---|
| 69 | end |
|---|
| 70 | end |
|---|
| 71 | |
|---|
| 72 | # キャッシュファイルのパスを取得する |
|---|
| 73 | def my_hotentry_dbfile |
|---|
| 74 | cache_dir = "#{@cache_path}/hatena" |
|---|
| 75 | Dir::mkdir(cache_dir) unless File::directory?(cache_dir) |
|---|
| 76 | "#{cache_dir}/my_hotentry.dat" |
|---|
| 77 | end |
|---|
| 78 | |
|---|
| 79 | # 人気の日記一覧を表示する |
|---|
| 80 | def my_hotentry(count = 5) |
|---|
| 81 | dbfile = my_hotentry_dbfile |
|---|
| 82 | hotentry = MyHotEntry.new(dbfile) |
|---|
| 83 | r = %Q|<ul class="rss-recent">\n| |
|---|
| 84 | hotentry.entries[0...count].each do |entry| |
|---|
| 85 | entry_link = %Q|<a href="#{entry[:url]}">#{CGI::escapeHTML(entry[:title])}</a>| |
|---|
| 86 | escape_url = entry[:url].gsub(/#/, '%23') |
|---|
| 87 | b_image = "http://b.hatena.ne.jp/entry/image/#{escape_url}" |
|---|
| 88 | b_link = "http://b.hatena.ne.jp/entry/#{escape_url}" |
|---|
| 89 | b_title = "このエントリを含むはてなブックマーク" |
|---|
| 90 | bookmark_link = %Q|<a href="#{b_link}" title="#{b_title}"><img border="0" src="#{b_image}"></a>| |
|---|
| 91 | r << "\t\t<li>#{entry_link} #{bookmark_link}</li>" |
|---|
| 92 | end |
|---|
| 93 | r << %Q|</ul>| |
|---|
| 94 | r << %Q|<div class="iddy"><span class="iddy-powered">\tPowered by <a href="http://b.hatena.ne.jp/entrylist?url=#{@conf.base_url}&sort=#{@conf['my_hotentry.sort']}">Hatena Bookmark</a></span></div>\n| |
|---|
| 95 | end |
|---|
| 96 | |
|---|
| 97 | # 人気の日記一覧を更新する |
|---|
| 98 | def my_hotentry_update |
|---|
| 99 | dbfile = my_hotentry_dbfile |
|---|
| 100 | hotentry = MyHotEntry.new(dbfile) |
|---|
| 101 | hotentry.update(@conf.base_url, |
|---|
| 102 | :html_title => @conf.html_title, |
|---|
| 103 | :sort => @conf['my_hotentry.sort']) |
|---|
| 104 | end |
|---|
| 105 | |
|---|
| 106 | if __FILE__ == $0 |
|---|
| 107 | # コマンドラインから実行した場合 |
|---|
| 108 | # tdiary.conf に base_url を設定しないと動作しない |
|---|
| 109 | begin |
|---|
| 110 | require 'tdiary' |
|---|
| 111 | rescue LoadError |
|---|
| 112 | STDERR.puts "tdiary.rb not found." |
|---|
| 113 | STDERR.puts "please execute in tdiary base directory" |
|---|
| 114 | exit 1 |
|---|
| 115 | end |
|---|
| 116 | cgi = CGI::new |
|---|
| 117 | @conf = TDiary::Config::new(cgi) |
|---|
| 118 | @cache_path = @conf.cache_path || "#{@conf.data_path}cache" |
|---|
| 119 | my_hotentry_update |
|---|
| 120 | puts my_hotentry |
|---|
| 121 | else |
|---|
| 122 | # 人気の日記一覧を取得する (日記更新時) |
|---|
| 123 | add_update_proc do |
|---|
| 124 | # ツッコミ時は実行しない |
|---|
| 125 | if @mode == 'append' or @mode == 'replace' |
|---|
| 126 | begin |
|---|
| 127 | my_hotentry_update |
|---|
| 128 | rescue |
|---|
| 129 | end |
|---|
| 130 | end |
|---|
| 131 | end |
|---|
| 132 | end |
|---|