過去のはてなブックマークからランダムで1件選んでツイートするBot
「あとで読む」タグを付けて放置しっ放しのブックマークが数多く溜まっている現状を打破したいと思い、Twitterのbotを実装しました。
具体的には、はてなブックマークの全ブックマークの中からランダムで1件を選択し、その情報をTwitterにツイートします。
過去のはてなブックマークからランダムで1件選んでツイートするBot · GitHub
ブックマークの情報をどこから取得するか
フィードが提供されているのでこれを使います。
はてなブックマークフィード仕様 - Hatena Developer Center
全ブックマーク数の取得
フィードをRSS形式で取得した場合、 "opensearch:totalResults" 要素に全ブックマーク数が含まれています。
RubyのRSSライブラリではこの値は取得出来ないため、Nokogiriを使ってゴリゴリXMLとして読み込みます。
... <channel rdf:about="http://b.hatena.ne.jp/aquarla/"> <title>あくあーらのブックマーク</title> <link>http://b.hatena.ne.jp/aquarla/</link> <description>あくあーらのブックマーク</description> <opensearch:startIndex>1</opensearch:startIndex> <opensearch:itemsPerPage>20</opensearch:itemsPerPage> <opensearch:totalResults>3295</opensearch:totalResults> <items> ...
ブックマークの情報をランダムで1個だけ取得
全ブックマーク数をもとに、「n番目のブックマークを取得する」のnをランダムで決定し、当該ブックマークの情報をピンポイントで取得します。
はてなブックマークフィードのドキュメントには、 "of"パラメータを付けることでページングが可能と書いています。
http://b.hatena.ne.jp/aquarla/rss?of=20
ちなみに、ドキュメントに明記されていない(実際に動かしてみるとわかる)仕様がいくつかあって…。
- 1ページで取得出来る件数は20件固定。減らすことも増やすことも出来ない
- "of"パラメータは自動的に20の倍数に切り詰めて扱われる。(たとえば"of=30"を指定しても、20に切り詰めて扱われる)
title、link、description要素はRSSライブラリでも取得出来るのですが、ブックマーク数の取得にNokogiriを使っていてここで別のライブラリを持ち出すのもアレなので、そのままNokogiriでゴリゴリ取得してしまっています。
ブックマーク情報をツイート
取得したブックマーク情報(タイトル、URL、コメントなど)をTwitterにツイートします。
- ライブラリはなんでも良いと思うのですが、ここではRuby OAuth Gemを使っています。
- 文字列内に"@"が含まれていたりすると迷惑になるかもなので、ツイート前に予め取り除きます。
- タイトルやコメントが長すぎる場合は切り詰めます。あまりゴツいライブラリはrequireしたくなかったので、文字列切り詰めの処理部分だけをActiveSupportから無理やりパクってきました。それが嫌な人はActiveSupportをそのまま使いましょう。
ソースコード
#!/usr/bin/env ruby # -*- coding: utf-8 -*- require 'rubygems' require 'open-uri' require 'nokogiri' require 'oauth' # ActiveSupportのString#truncateを参考にする # String#mb_charsは、Ruby1.9前提であればそのままselfを返してよい class String def truncate(length, options = { }) text = self.dup options[:omission] ||= "..." length_with_room_for_omission = length - options[:omission].mb_chars.length chars = text.mb_chars stop = options[:separator] ? (chars.rindex(options[:separator].mb_chars, length_with_room_for_omission) || length_with_room_for_omission) : length_with_room_for_omission (chars.length > length ? chars[0...stop] + options[:omission] : text).to_s end def mb_chars self end end # 過去のブックマークからランダムに1個を選びツイートするbotクラス module HatenaBookmarkBot class Base # Twitterアクセスのための各種情報 CONSUMER_KEY = "XXXXXXXXXXXXXXXXX" CONSUMER_SECRET = "XXXXXXXXXXXXXXXXX" ACCESS_TOKEN = "XXXXXXXXXXXXXXXXX" ACCESS_TOKEN_SECRET = "XXXXXXXXXXXXXXXXX" # はてなブックマークのユーザー名 HATENA_BOOKMARK_USERNAME = "aquarla" # はてブRSSをXMLとして取得 # offsetを指定した場合は、offset件目以降を取得 def open_hatena_bookmark_rss(username, offset=nil) rss_url = "http://b.hatena.ne.jp/#{username}/rss" rss_url += "?of=#{offset}" if offset open(rss_url) do |file| yield file.read end end # XMLから現在の総ブックマーク数を取得 def bookmark_count(xml) namespaces = { 'opensearch' => 'http://a9.com/-/spec/opensearchrss/1.0/', } Nokogiri::XML.parse(xml).at('.//opensearch:totalResults', namespaces).text.to_i rescue -1 end # 文字列をツイート def tweet(status) consumer = OAuth::Consumer.new(CONSUMER_KEY, CONSUMER_SECRET, :site => 'https://api.twitter.com') access_token = OAuth::AccessToken.new(consumer, ACCESS_TOKEN, ACCESS_TOKEN_SECRET) access_token.post('/1.1/statuses/update.json', 'status' => status) end # 処理本体 def run # 1. はてブのRSSフィードから、ブックマークの全件数を取得 open_hatena_bookmark_rss(HATENA_BOOKMARK_USERNAME) do |xml| count = bookmark_count(xml) # 2. 全件のうち何件めをツイートするかをランダムに決定し、 index = rand(count) open_hatena_bookmark_rss(HATENA_BOOKMARK_USERNAME, index/20*20) do |xml2| namespaces = {"rss" => "http://purl.org/rss/1.0/"} item = Nokogiri::XML.parse(xml2).xpath("//rss:item[#{(index%20)}]", namespaces) title = item.xpath(".//rss:title", namespaces).text link = item.xpath(".//rss:link", namespaces).text description = item.xpath(".//rss:description", namespaces).text # 3. ブックマーク情報をツイートする # ツイートに@が含まれていると迷惑なので外すのと、 # ツイートが長すぎる場合はタイトル及びコメントを切り詰める status = if description.nil? || description == "" "#{title.truncate(100)} #{link}" else "#{description.truncate(50)} / #{title.truncate(50)} #{link}" end status.gsub!(/@/, "") tweet(status) end end end end end if $0 == __FILE__ HatenaBookmarkBot::Base.new.run end
おわりに
上記のプログラムを、どこかのサーバで定期的にcron実行するようにすればOK。
「あとで読む」を消化するために実装したbotですが、今まですっかり忘れていた事項をプッシュで配信してくれるので、意外と便利です。
遠い昔のブックマークをツイートすることも多く、色々と紛らわしいので、protectedでの運用を推奨します。