私の Google リーダーの共有アイテムの Atom フィード を確認すると、次のようになってる。
<?xml version="1.0"?>
<feed xmlns:idx="urn:atom-extension:indexing" xmlns:media="http://search.yahoo.com/mrss/" xmlns:gr="http://www.google.com/schemas/reader/atom/" xmlns="http://www.w3.org/2005/Atom" idx:index="no" gr:dir="ltr">
<!--
Content-type: Preventing XSRF in IE.
-->
<generator uri="http://www.google.com/reader">Google Reader</generator>
<id>tag:google.com,2005:reader/user/09691029617997612992/state/com.google/broadcast</id>
<link rel="hub" href="http://pubsubhubbub.appspot.com/"/>
<title>Sadayuki's shared items in Google Reader</title>
<gr:continuation>COPW37O0_KIC</gr:continuation>
<link rel="self" href="http://www.google.com/reader/public/atom/user/09691029617997612992/state/com.google/broadcast"/>
<author>
<name>Sadayuki</name>
</author>
<updated>2010-07-28T15:02:15Z</updated>
ソースは改行されていないので見難いが、<link rel="hub" href="http://pubsubhubbub.appspot.com/"/> となっているのが分かる。PubSubHubbub 対応しているということだ。
Google のデモサーバの Subscribe のページ を見ると、次の項目が必要なことが分かる。
- Callback: (the subscriber URL)
- Hub からのコールバックを受ける URL を入力する。Subscribe するにはこれを受け付けるようにする必要がある。
- Topic: (the feed URL)
- Subscribe したい URL を入力する。今回の場合、http://www.google.com/reader/public/atom/user/09691029617997612992/state/com.google/broadcast になる。
- Verify type:
- コールバックを同期で受けるか、非同期で受けるかを伝える。同期にすると、Subscribe が成功したかどうかがすぐに分かる。
- Mode:
- 購読の申込と、取り消しを指示する。
- Verify token:
- コールバックのリクエストに含まれるので、コールバックが正規のリクエストによるものかを判定するのに使える。
- HMAC secret: (optional)
- これは今回使わないので調べてない。
で、上記の項目値を見ると分かるように、コールバックを受ける URL を用意する必要がある。Ping と違って簡単で無い。
Hub からのコールバック は、指定した URL に GET リクエストが来る。例えば次のような。
http://www.kurano.jp/blog/sadayuki/pubsub/?hub.mode=subscribe&hub.topic=http://www.google.com/reader/public/atom/user/09691029617997612992/state/com.google/broadcast&hub.challenge=random&hub.verify_token=secret
リクエストを受けた側は、自分が要求したものかどうかを、hub.topic や hub.verify_token を使って確認し、問題ない場合は hub.challenge をレスポンスする。
なので、Sinatra だと次のような感じ。本当はもっとチェックを厳しくしたほうが良いと思うが、私のサイトを攻撃しようとする人もあまり居ないだろうという前提で、簡単にしてある。まあ、普段はこの URL でリクエストを受けないようにしておき、同期モードで Subscribe するときだけ開けておけば OK 。
get '/pubsub' do
if params["hub.verify_token"] == "secret"
"#{params["hub.challenge"]}"
else
"ERROR"
end
end
こうして無事、Subscribe リクエストが通ると、今度は 新しい記事が配信 されてくる。今度は、コールバック URL (上記例では http://www.kurano.jp/blog/sadayuki/pubsub)に POST リクエストが来る。Google リーダー の場合、このリクエスト Body に、新しい記事の Atom Feed が載って来る。なので Body を処理するか、元々の URL に再度アクセスするかで、Google リーダーの共有アイテムを取得できる。
直接処理する場合は、リクエスト元が、Subscribe リクエストをした先であるのかどうかなど、チェックをした方が良いだろう。
post '/pubsub' do
doc = REXML::Document.new(request.body.read)
REXML::XPath.each(doc, '//entry') do |e|
n = Note.new
n.google_id = e.elements["id"].text
n.title = e.elements["title"].text
n.content = e.elements["gr:annotation/content"].text
n.uri = e.elements["link[@rel='alternate']/@href"].value
begin
n.save
rescue ActiveRecord::StatementInvalid
end
end
end
適当に書くとこんな感じ。DB に格納された内容を元に tweet すれば、Note の内容が即 tweet されることになる。素晴らしい!