2011年3月18日

Google Buzz で Activity Streams (2)

昨年末に Google Buzz で Activity Streams (2010年12月の記事) を試してみました。 当時は認可の形式が OAuth 1.0 ベースでしたが、最近になって Google が OAuth 2.0 (Draft 10) に対応し始めました。 それに合わせて google-api-python-client にもモジュールが追加されましたので、試用してみます。

なお、InfoQ の記事にもある通り、OAuth 2.0 の最新版は Draft 13 なので、ライブラリのコードは変更されていくかもしれません。

Buzz にデータを流す

とりあえず、何もデータが存在しないと寂しいので Buzz にデータを投稿します。 Gmail を Web ブラウザから使っている場合には、Buzz を有効化すると「受信箱」(Inbox) の下に Buzz が出現します。 メニューで Buzz を選択すると投稿用のフォームがありますので、そこに適当なメッセージを入力して送信できます。 送信するときには公開範囲を選択できます。

いちいちブラウザでメールボックスを開かないよ... という場合には Twitter へのポストを Buzz に流すこともできます。 クローリング (?) も早く、おそらくリアルタイム検索と同じくらいのタイムラグ (つまり、すぐさま) で反映されます。

もうひとつ、自分がちょっと良いかも、と感じているのが、Google Reader からの共有設定です。 新聞の代わりにフィードを読んでいるような人だと、1日当たりに数百から千件くらいの情報 (もっと多いかも) に目を通すと思いますが、 情報量が多くなるとそれを選別することが大変になります。 「あとで読む」という場合にはスターを付けることが一般的だと思いますが、 「読み終わってスクラップブックに保存したい」場合にはどうでしょうか。 ひとつの方法は、ブックマークに登録することになるでしょう。 ブラウザのブックマーク、ブラウザ間の同期サービスや Evernote、ソーシャルブックマークサービスなど、いくつかの方法があります。 Google Reader からの共有がちょっと便利なのは、ブラウザ間で同期させる必要がなく、携帯電話からも使えて、別のサービスにジャンプする必要がないことです。 あと、キーボードショットカット ("S") が使えるのも嬉しいところです。 記事を読むときは一気にまとめて読んでしまい、ちょっと気になったことを共有しておくと、 一日の終わりや週の終わりにその期間のことを振り返りやすくなります。

ということで、こじつけ感が強いことは否めませんが、Buzz にデータを流せるようにしておきます。

OAuth 2.0 での認証

Google API Python Client (2010年11月の記事) をインストールします。 Mercurial で pull (https://google-api-python-client.googlecode.com/hg/) したディレクトリで、サンプルを実行します。 README は更新が追いついていないと思いますので読まなくとも。

実行するのは次の二行 (PYTHONPATH=. python samples/oauth2/buzz/buzz.py なら一行) です。

$ source setpath.sh
$ python samples/oauth2/buzz/buzz.py

基本的なフローは前述のブログ記事にある通りですが、OAuth 2.0 では「確認コード」を要求されません。 代わりに次のメッセージが表示され、コマンドライン側では認証が完了しています。

The authentication flow has completed.

これが OAuth 2.0 の嬉しいところですが、細かい部分は省略します。 実装は oauth2client.tools.run にあると思いますので、時間があるときにでも。 リダイレクトして戻って来た場合の HTTP サーバをローカルで立ち上げています。

認証フローが終わると buzz.dat というファイルが生成されます。 認証トークンなどを保持している Python オブジェクト (oauth2client.client.OAuth2Credentials) を pickle でシリアライズしたものです。

ひとつ注意すべきなのが、このサンプルは認証が成功すると書き込み処理も実行することです。

  1. 最新の Activity を二件取得する。
  2. その次の二件を取得する。
  3. 新しく Activity を POST する。
  4. その Activity にコメントを追加する。

とはいえ Web 画面からすぐに削除も変更もできますので、ちょっと buzz を撒き散らしてスミマセン、ということで。

NOTE:

Python で OAuth を扱う上で紛らわしいのが、モジュール名です。 oauth は OAuth 1.0 系のレファレンス実装で、 oauth2 はその改良版です。 モジュール名に 2 が付きますが、仕様のバージョンは 1 系です。 Google のライブラリにおける OAuth 2.0 用のモジュール名は oauth2client です。 ソースツリーには oauth2oauth2client の両方が存在します。

Activity の取得

それでは Activity Streams の解析を... といきたいところですが、 Google の新しい API は discovery の仕組みを使います。 これに関しては GData 3 の discovery の中身 (2010年11月の記事) を少しだけのぞいて、 apiclient.discovery.build の使い方をつかめれば良いと思います。

さて、 build() でサービスのインスタンスを受け取ると、 activities() メソッドの戻り値を使って Activity Streams を操作できます。

def main():
  storage = Storage('buzz.dat')
  credentials = storage.get()

  http = httplib2.Http()
  http = credentials.authorize(http)

  service = build("buzz", "v1", http=http, developerKey="XXXXXXXX")
  activities = service.activities()

Activity Streams の Resource に対しては CRUD を基本とした操作 を実行できます。 一覧を取得するためには list 、新規にポストするためには insert などです。

たとえば、自分が Buzz に投稿した Activity を10件取得するためには次のコードを実行させます。 scope='@consumption' にすると、自分がフォローしているユーザーの Activity も取得します。

activitylist = activities.list(
    max_results='10', scope='@self', userId='@me').execute()

Activity のストリーム (=流れ) ですから、10件取得してもそれで終わりだとは限りません。 たいていの場合は、何らかの条件を満たすまで、同じように Activity を手繰り寄せたくなると思います。 Resource クラスでは list_next() がこれを実現してくれます。

if activitylist:
  activitylist = activities.list_next(activitylist).execute()

取得した activitylist は Python の辞書形式になっており、 items キーで Activity の一覧にアクセスできます。

writer = sys.stdout
for item in activitylist['items']:
  print >>writer, item['updated'], item['object']['content'].encode('utf-8')

item には source キーがあり、これによって投稿元の情報が分かります。 例えば、Twitter から流しているアイテムの元のリンクを表示させたい場合は次のようになります。

if 'source' in item and item['source']['title'] == 'Twitter':
    tag = item['crosspostSource']
    href = tag[tag.rfind(':') - 4:]  # 4-char back for "http"
    print >>writer, "Original:", href

あとはこれらを整形してあげれば活動のまとめを生成できます。 旅行の記録や日常の日記の下地としては便利ではないでしょうか。

終わりに

Google Buzz にデータを投稿し、Activity Streams を受け取りました。 google-api-python-client を使うと簡単に API を実行できます。 XML の細部に立ち入る必要がないのは嬉しいことです。

Google でも OAuth 2.0 をサポートし始めたことにより、ライブラリにも機能が追加されています。 ライブラリのレベルでキレイに抽象化されていますので、アプリケーション開発者視点では特に違いを意識することなく認証フローを実装できます。 アプリケーションの利用者にとっては、OAuth 2.0 によって PIN コードを入力する必要がありませんので、ひと手間省けて導入の敷居が下がることが期待できます。

情報の流量が増加するとそれを整理することが大変になってしまい、たくさんの Web サービスを使い始めると情報が発散しがちです。 サービスによっては、操作感の違いに戸惑うこともあるかもしれませんし、サポートされるデバイスが少ないかもしれません。 しかし、最近はどのサービスも API を整備してきており、複数のサービスの情報をひとつのストリームに束ねることも現実的になってきました。 その選択肢のひとつとして Google Buzz は便利だな、と思います。 あとはソーシャルグラフなのかなぁ、と思いますが、それが一番大変だったり...

とはいえ、今回の大震災は多くの人が連絡手段の冗長性について考える契機になったはずです。 電話がダメならメール、メールがダメなら Twitter、Twitter がダメなら PFIF (People Finder Interchange Format)、 PFIF で追いつかなければ人力での文字起こし... 結局、最後は人と人とのつながりですが、それをまとめる一助として Activity Streams が使えるかもしれませんね。

おまけ

Python の pprint モジュールを使って activitylist 変数を書き出してみると次のデータを得られます。 コマンドラインからポストしたものと、Twitter から流したものです。

{u'id': u'tag:google.com,2010:buzz-feed:self:posted:XXXXXXXX',
 u'items': [{u'actor': {u'id': u'XXXXXXXX',
                        u'name': u'Shigeru Kitazaki',
                        u'profileUrl': u'XXXXXXXX',
                        u'thumbnailUrl': u'XXXXXXXX'},
             u'id': u'tag:google.com,2010:buzz:XXXXXXXX',
             u'kind': u'buzz#activity',
             u'links': {u'alternate': [{u'href': u'XXXXXXXX',
                                        u'type': u'text/html'}],
                        u'liked': [{u'count': 0,
                                    u'href': u'XXXXXXXX',
                                    u'type': u'application/json'}],
                        u'replies': [{u'count': 2,
                                      u'href': u'XXXXXXXX',
                                      u'type': u'application/json',
                                      u'updated': u'2011-03-17T09:32:07.656Z'}],
                        u'self': [{u'href': u'XXXXXXXX',
                                   u'type': u'application/json'}]},
             u'object': {u'content': u'XXXXXXXX',
                         u'links': {u'alternate': [{u'href': u'XXXXXXXX',
                                                    u'type': u'text/html'}]},
                         u'originalContent': u'XXXXXXXX',
                         u'type': u'note'},
             u'published': u'2011-03-17T09:28:09.000Z',
             u'source': {u'title': u'Google API Client Example App'},
             u'title': u'XXXXXXXX',
             u'updated': u'2011-03-17T09:28:09.580Z',
             u'verbs': [u'post'],
             u'visibility': {u'entries': [{u'id': u'G:@me:@public',
                                           u'title': u'Public'}]}},
            {u'actor': {u'id': u'XXXXXXXX',
                        u'name': u'Shigeru Kitazaki',
                        u'profileUrl': u'XXXXXXXX',
                        u'thumbnailUrl': u'XXXXXXXX'},
             u'crosspostSource': u'tag:twitter.com,2007:XXXXXXXX',
             u'id': u'tag:google.com,2010:buzz:XXXXXXXX',
             u'kind': u'buzz#activity',
             u'links': {u'alternate': [{u'href': u'XXXXXXXX',
                                        u'type': u'text/html'}],
                        u'liked': [{u'count': 0,
                                    u'href': u'XXXXXXXX',
                                    u'type': u'application/json'}],
                        u'replies': [{u'count': 0,
                                      u'href': u'XXXXXXXX',
                                      u'type': u'application/json',
                                      u'updated': u'2011-03-17T09:02:12.000Z'}],
                        u'self': [{u'href': u'XXXXXXXX',
                                   u'type': u'application/json'}]},
             u'object': {u'content': u'XXXXXXXX',
                         u'links': {u'alternate': [{u'href': u'XXXXXXXX',
                                                    u'type': u'text/html'}]},
                         u'originalContent': u'XXXXXXXX',
                         u'type': u'note'},
             u'published': u'2011-03-17T09:02:12.000Z',
             u'source': {u'title': u'Twitter'},
             u'title': u'XXXXXXXX',
             u'updated': u'2011-03-17T09:02:12.000Z',
             u'verbs': [u'post'],
             u'visibility': {u'entries': [{u'id': u'G:@me:@public',
                                           u'title': u'Public'}]}}],
 u'kind': u'buzz#activityFeed',
 u'links': {u'next': [{u'href': u'XXXXXXXX'}],
            u'self': [{u'href': u'XXXXXXXX',
                       u'type': u'application/json'}]},
 u'title': u'Google Buzz Self Feed for Shigeru Kitazaki',
 u'updated': u'2011-03-17T09:32:07.656Z'}

XML の名前空間の違いを意識することなく変換してくれますので、 とっかかりとしては分かりやすいと思います。

コメントを投稿