GAE/jからWeb APIを通してDiigoのブックマークをGETする

概要

現在、Diigoというブックマークサービスを起点として、様々な類似サービスにクロスポストするプログラムをGAE上に構築中です。

今回はその第一歩として行った、Diigoからのブックマーク読み出しについてまとめました。

主な内容は以下の通りです。

  1. GAE/jからのBASIC認証を使ったAPI読み出し
  2. JSON形式の受け取り
  3. 低レベルAPIを使ったURLの訂正

はじめに(読まなくても大丈夫です)

はじめに、なんでこんな事をしているのか、少し事情を説明しましょう。

私は普段RSSを消化しながら、はてなブックマークTwitterTumblrFacebookEvernote、そしてDiigoへと気に入った記事をブックマークしています。

いきなり余談ですが、私は特にDiigoを気に入っています。Diigoでどんな事ができるかは、Diigoのトップにある動画を見てもらえば分かると思います。

さてそれで、これまではこのクロスポストを実現する為に、Chrome拡張であるTaberarelooを利用させて頂いておりました。

しかし、それにも問題がありました。それは、facebookDiigoに対応していない事……ではありません。そんな事は、些細な問題です。私にとってより大きな問題は、taberareloo自身が、個々のサービスへの認証機能を持たない事でした。

この為私は、毎日RSSリーダを開くと同時にtwitterへログインし、Enernoteのログイン状態をチェックし、tumblrは稼働中かを確認し……。そうしてようやく、記事のチェックに入っていた訳です。

これが、実に面倒くさい。

これを解消する他の方法は無いかと思いましたが、見つかりません。そして、私は思いました。

「無いなら作れば良いじゃない。D・I・Y! D・I・Y!」

運が良いのか悪いのか、近頃自分はGAEで遊んでいます。それならばと、まずは手始めに、Diigoからブックマークを読みだす事に挑戦したのでした。

尚、起点はDiigoとした理由は、それが最も多機能であったからです。他のサービスを起点としてしまうと、Diigoのマーカ機能や付箋機能が活かせません。その為自然と、Diigoを起点とする事になりました。

DiigoAPI仕様とその呼び出し

DiigoAPI仕様は、以下のページにまとめられています。
Diigo API Documentation

私は初めこれを見て、愕然としました。何故ならこのDiigoAPI、Basic認証*1しかサポートしていなかったのです。

Diigo API uses HTTP Basic authentication - a standard authentication method that includes base64 encoded username and password in the Authorization request header. Developers must NOT use the credentials provided by users for any other purposes. Other authentication options such as OAuth may be supported in future.

とはいえ、接続にはhttpsが利用出来る為、自分自身で利用する分には心配ありません。ここではそれで良しとしましょう。

さて、今回利用するのはブックマークの読み出しです。これについて、仕様ではこう書いてあります。

URL: https://secure.diigo.com/api/v2/bookmarks
Request method: GET

Parameters
user ;required, string, the username of whose bookmarks to fetch
count ;optional, number, the number of bookmarks to fetch, defaults to 10, max:100

他にもいくつかオプションはありますが、今回使用したのはこの二つだけです。

そして、リクエストの例として以下が示されています。

https://secure.diigo.com/api/v2/bookmarks?user=joel&count=10

ここで改めて書かれてはいませんが、このURLにBASIC認証で署名したGETメソッドを送る事で、ブックマークが取得できます。……公式の説明は、若干不親切な気もしますね。

以上、認証からGETまでをGAE/j上で実装した例が、以下のコードになります。

int SUCCESS_Code = 200;
String urlString = "https://secure.diigo.com/api/v2/bookmarks";
String option = "?user=<user>&count=20";
String auth = "<user>:<password>"

//URL生成
StringBuilder urlStrBuilder = new StringBuilder(urlString);
urlStrBuilder.append(option);
URL url = new URL(urlStrBuilder.toString());
HttpURLConnection connection =  (HttpURLConnection)url.openConnection();

//認証情報追加
String authHeader = "Basic " + Base64.encode(auth.getBytes());
connection.setRequestProperty("Authorization", authHeader);

//接続
if (connection.getResponseCode() == SUCCESS_Code) {
	// Processing
} else {
	// Server returned error code.
}

あくまで例なので、例外処理などは省きました。また、はブックマークを取得したいユーザ名に、は適切なパスワードに置き換えて下さい。

URLフェッチについては、公式ドキュメントも参考になります*2

レスポンスで返ってきたJSONを処理する

API読み出しがうまくいった場合、JSON形式*3で次の様な応答が返ってきます。

[
{
"title":"Diigo API Help",
"url":"http:\/\/www.diigo.com\/help\/api.html",
"user":"foo",
"desc":"",
"tags":"test,diigo,help",
"shared":"yes",
"created_at":"2008/04/30 06:28:54 +0800",
"updated_at":"2008/04/30 06:28:54 +0800",
"comments":,
"annotations":

},
{
"title":"Google Search",
"url":"http:\/\/www.google.com",
"user":"bar",
"desc":"",
"tags":"test,search",
"shared":"yes",
"created_at":"2008/04/30 06:28:54 +0800",
"updated_at":"2008/04/30 06:28:54 +0800",
"comments":,
"annotations":

}
]

これを読み出す為のコードは、公式に従って、以下の様に実装しました。

InputStream input = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(input,"UTF-8"));
String line;
while ((line = reader.readLine()) != null) {
	jsonResponse.append(line);
}
reader.close();

JSONは非常にシンプルな形式を持つため、全て自前での処理も可能そうではありますが、やはりここはライブラリに頼るべきでしょう。今回はそのライブラリとして、google-gsonを選択しました。これは、JSON形式からJavaオブジェクトへの変換が容易である事が大きな理由です。(そして名前が気に入りました)

GSONを使ったJavaオブジェクトへの変換は非常に簡単で、僅か二行で完了します。

Gson gson = new Gson();
DiigoJsonObj[] diigoBookMarks = gson.fromJson(jsonResponse.toString(), DiigoJsonObj[].class);

Gsonのインスタンスをわざわざ保持しなければ、一行ですね。

ただしこの時、事前に以下のクラスを作って置きました。

public class DiigoJsonObj {
    private String title;
    private String url;
    private String user;
    private String desc;
    private String tags;
    private String shared;
    private String created_at;
    private String updated_at;
    private DiigoJsonAnnotation[] annotations;
}
public class DiigoJsonAnnotation {
    private String user;
    private String content;
    private String created_at;
}

本当はDiigoからの応答に"comments"という属性もあるのですが、これが何なのかこの時は分からなかった事、また分かったとしても不要そうである事から、今回のプログラムではあえて無視しました。

以上で、Diigoからの応答を処理して、Javaオブジェクトにできました。あとは各自自分のプログラムに合わせて処理していきましょう。

URLの訂正

このプログラムを作っていて分かったのですが、どうやらDiigoはURLの末尾に"/"があった場合、それを削除してしまう様です。ブックマークサービスとして、どうかと思う仕様です。これでは今後別のブックマークサービスにPOSTする時に困りますので、これを直す方法も考えました。

(1/17 以下部分に誤りがあった為訂正しました)
その方法とは、以下のような物です。

  1. Diigoから読み出したURLにHEADメソッドでアクセス
  2. 2xx(成功)のステータスコードが得られたら、元のURL又は最終到達URLを取得して終了
  3. 4xx(失敗)のステータスコードが得られたら、URLの最後に"/"を付加して終了

2.で最終到達URLを使用するのは、相手サーバの仕様で、"/"が足りない場合正しいURLへリダイレクトされる事があるからです。こちらのプログラムでリダイレクトを拒否しても良かったのですが、それに従ったほうが実装が楽かと思い、なすがままに付いていく事にしました。

これを実装した例が、以下のコードになります。

private String correctURL(String urlString){
    //接続してチェック
    HTTPResponse response = checkURL(this.url);

    //2xxなら良し
    if(response.getResponseCode()/100 == 2){
        //Finalが無い場合はそのまま
        if(response.getFinalUrl() == null){
            return this.url;
        }
        else{
            return response.getFinalUrl().toString();
        }
    }
    //4xx系のエラーに対しては/を追加して返す
    else if(response.getResponseCode()/100 == 4){
        return this.url.concat("/");
    }

    return "";
}

private HTTPResponse checkURL(String urlString){
	URL url = new URL(urlString);
	HTTPRequest urlRequest = new HTTPRequest(url, HTTPMethod.HEAD);
	HTTPResponse urlResponse = URLFetchServiceFactory.getURLFetchService().fetch(urlRequest);
}

(1/17 訂正ここまで)

こちらでは、特に複雑なHTTP呼び出しは存在しない為、GAE/jの低レベルAPIを利用しました*4

尚、このコードは現在テスト中な為、今後変わることもあり得ます。

まとめ

今回はGAE/j上で、以下の三つの事を行いました。

  1. BASIC認証を使ったAPI読み出し
  2. JSON形式の受け取りとJavaオブジェクト化
  3. 低レベルAPIを利用したURL存在チェック

これにて他のサービスへポストする準備が整いましたので、順次各サービスへのポストを実装していきたいと思います。
というか既に、はてなブックマークへのポストは実装済みですので、そちらに関しては近日中にまとめて記事にします。