えいのうにっき

主に Web 系技術ネタ。背景画像 is powered by grass-graph.shitemil.works

Java(GAE/J)ではてなOAuth/対応APIを利用する

発端は、勉強会などで渡すための、プライベート用の名刺でした。(しばらく本題とは直接関係のない話になります、すみません)




先日、「CSS Nite 2012 in OKAYAMA」に参加してきまして。その懇親会の場でも、何枚か名刺をお渡しさせて頂いたのですが。
今使っている名刺の裏面には、ちょっとした宣伝の意味で自作WebサービスのURLを印刷してたんですが、最近そのURLが変わったり、「これも書いておきたいな」というサービスが増えたこともあって、下の写真のように、手書きで修正してたんですね。



これ、修正作業のあいだは特に何も思わなかったんですが、いざ名刺交換、となると、ちょっと恥ずかしかったんですね。w 手書き修正作業そのものも、結構たいへんで面倒だったし。

かといって、単に名刺を新しく作り直すだけでは、今後また同じようなこと(名刺に入れておきたいことが増える)が起こった場合、再度作りなおさなきゃいけなくなる。そもそもそれを許すにしても、名刺という限られたスペースが、文字列でどんどんゴチャゴチャしてきてしまって、美しくない。
となると、「名刺を渡した相手に知ってもらいたい、自分に関すること」を一元化してまとめてあるような場所を作って、その場所だけを書いておけばいいな、と。「名刺のURLを見てアクセスしてくれた人=自分に興味を抱いてくれた人」に対して、自分に関する情報をまとめたページがあればいいな、と。


こういうとき、普通はFacebookだったりブログだったりするんだろうけど、上のような目的を持たせるということを考えると、自分的にはどっちも、ビミョーに違うんですよね・・・うまく言い表せないんですが。あくまでぼく的に、です。
Facebookはあまりにもプライベート色が強い情報を含んでいるし、ブログはそこにあるエントリーが主役。もちろん、ブログの各エントリーを見ていってもらえばぼくという人となりはよくわかってもらえると思うんだけど、短い時間で自分のことを知ってもらうという役目を負わせるのには不向き。理想は、自分に関する基本情報をはじめとして、最近のつぶやき、ブクマなど、「こちらが意識してネット上にアウトプットしている活動の記録」がそこに集約されているようなものがベストで、加えて、ある程度カスタマイズができれば、言うことなし。


てなわけで、ここ数日でこんなのつくった。



えいのうのいえ - a-know's home



「えいのうのいえ」・・・「a-knowの家」、ということで。変なサイト名ですが、いろいろ考えてたら、「えいのうのいえ」が回文になるのがわかって、テンションが上がっちゃって。んで、そのまま採用。笑 Google App Engine上に作っています。


簡単な自己紹介、自作アプリについての紹介をはじめとして、最近のつぶやき、はてブ、イマココ、新着ブログエントリ、読んだ本、写真、などを紹介できてます。はてブやはてダ、イマココなど、自分ははてなのサービスを中心に使っているもので、こういう構成になりました。「はてなone」が、タイムライン表示だけじゃなくて、こういう感じにできればよかったんだけど。
というかそもそも、ぼくが最初に「はてなone」というサービス名を聞いたとき、こういう感じのサービスなんだろうな〜と思ってたんですけどね〜。「はてなone」が、はてなユーザーのためのSNSになってくれてたら、こんなの自作する必要なかったのに。(まぁ、ゆくゆくはそういうふうにしてくつもりなのかもだけど・・・)


まぁ、もう作ってしまったので、愚痴はこのへんにして・・・。


で、ここからが本題なんですが(失礼しました)、特に「最近のブックマーク」「最近のイマココ」なんかは、今回のページの主旨のような「自分がどんな人間か」を表す上では、あった方が絶対にいいよな〜と思う一方で、それに対応したブログパーツがない(見つけられなかった)みたいなので、今回は、このページを置いているGAE/J上で、はてなOAuth/対応APIを利用することで、それらを実現させてみました。
JavaでOAuthっていうと、ぼくは「Twitter4Jを用いてのTwitter OAuth」しか経験がありませんでした。「まぁ、それでもそんなに苦労しないでしょ」などと高をくくっていたら、これが思いの外苦労したので、今日は、かなり独自な部分が多いとは思うのですが、「GAE/J上でのはてなOAuth/対応API利用」をどうやってやっているかを残しておこうかなと思います。

あと、結局「最近のはてブ」の方はAPIではなくRSSから取得するようにしたので、そちらもオマケで。

はてなOAuth認証Access Token / Access Token Secretを取得する

はてなOAuth認証には、TwitterのTwitter4Jのような便利なJavaライブラリは存在しません(多分)。なので、汎用的なJava OAuthライブラリを使って認証を行う必要があります。
いろんな方法を調べて、さらには実際に試してみたんですが、最終的にうまくできたのは「GroovyではてなOAuth」というエントリを参考にした方法でした。こちらのエントリではGroovyでしたが、まぁほぼJavaと同じってことで。
OAuthライブラリ・・・といっていいのかはわかりませんが、参照先と同じく、fernandezpablo85/scribe-java - GitHubを利用させて頂くことにしました。

「GroovyではてなOAuth」のエントリでは、OAuth10aServiceImplをrequireScopeを使うように改造されていましたが、ぼくはその方法ではちょっとうまくいかなかったので、 addOAuthParams(OAuthRequest request, Token token)を、アクセストークン取得用のものを別に作って、リクエストトークン取得時とアクセストークン取得時とで使い分けるような改造をしました。(はてなOAuthでは、パラメータのscopeはリクエストトークン取得時のみ必要なんですね)

そこまで準備ができたら、まずはAccess TokenとAccess Token Secretを取得します。「GroovyではてなOAuth」のエントリでは、アクセストークン取得後そのままAPIを呼び出していますが、ぼくの方ではまず、アクセストークンの取得に専念することにします。
あ、あらかじめ「OAuth開発者向け設定ページ」にて、アプリケーションの登録・Consumer Key/Secretの取得はしておいて下さい。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

import org.scribe.builder.ServiceBuilder;
import org.scribe.builder.api.DefaultApi10a;
import org.scribe.model.Token;
import org.scribe.model.Verifier;
import org.scribe.oauth.OAuthService;

public class oauthtest {

	public static void main(String[] args) throws IOException {

		DefaultApi10a api = new DefaultApi10a() {

			@Override
			public String getRequestTokenEndpoint() {
				return "https://www.hatena.com/oauth/initiate";
			}

			@Override
			public String getAuthorizationUrl(Token arg0) {
				return "https://www.hatena.ne.jp/oauth/authorize?oauth_token=" + arg0.getToken();
			}

			@Override
			public String getAccessTokenEndpoint() {
				return "https://www.hatena.com/oauth/token";
			}
		};

		OAuthService service = new ServiceBuilder().provider(api)
				.apiKey("予め取得しておいたConsumer Key").apiSecret("予め取得しておいたCosumer Secret").scope("read_private").build();

		Token reqToken = service.getRequestToken();

		System.out.println("以下のURLにアクセスし、PINを取得します。→" + service.getAuthorizationUrl(reqToken));
		System.out.println("PINを入力して下さい。\n");

		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		String pin = br.readLine();
		Verifier v = new Verifier(pin);

		// アクセストークンの取得
		Token accessToken = service.getAccessToken(reqToken, v);


		System.out.println("token:" + accessToken.getToken());
		System.out.println("secret:" + accessToken.getSecret());

		return;

	}

}

これを実行すると、「以下のURLにアクセスし、PINを取得します。→」の後ろにURLが出力されるので、そのURLにブラウザなどでアクセス、PINを取得します。
そしてその取得したPINをコンソールに入力することでアクセストークンが取得でき、Token / Secretが標準出力されます。


はてなOAuthに対応したAPIを利用して情報を取得する

続いて、はてなOAuthに対応したAPIを利用して情報を取得してみます。
以下の例では、「はてなココ」を題材に用いています。はてなAPI全般の仕様に関しては、こちらがよろしいかと思います。


import java.io.IOException;
import org.scribe.builder.ServiceBuilder;
import org.scribe.builder.api.DefaultApi10a;
import org.scribe.model.OAuthRequest;
import org.scribe.model.Response;
import org.scribe.model.Token;
import org.scribe.model.Verb;
import org.scribe.oauth.OAuthService;

public class ApiTest {

	public static void main(String[] args) throws IOException {

		DefaultApi10a api = new DefaultApi10a() {

			@Override
			public String getRequestTokenEndpoint() {
				return "https://www.hatena.com/oauth/initiate";
			}

			@Override
			public String getAuthorizationUrl(Token arg0) {
				return "https://www.hatena.ne.jp/oauth/authorize?oauth_token=" + arg0.getToken();
			}

			@Override
			public String getAccessTokenEndpoint() {
				return "https://www.hatena.com/oauth/token";
			}
		};

		OAuthService service = new ServiceBuilder().provider(api)
				.apiKey("予め取得しておいたConsumer Key").apiSecret("予め取得しておいたCosumer Secret").scope("read_private").build();

		Token accessToken = new Token("先程取得したAccess Token","先程取得したAccess Token Secret");

		OAuthRequest req = new OAuthRequest(Verb.GET, "http://c.hatena.com/api/v1/history.json?name=a-know&l=10");

		service.signRequest(accessToken, req);

		Response res = req.send();

		System.out.println("code:" + res.getCode());
		System.out.println("body:" + res.getBody());
	}
}


こうして得られるbodyの情報が、ここでの例だとJSON形式になっていますんで、それを踏まえて、対処してやればよいです。
出来てしまえば・後から振り返ると、簡単なんですけどね。。


RSSから、「最近ブックマークしたエントリ」の情報を取得する

最初はこっちもAPIでやっつける予定だったんですが、APIを使った場合、得られる情報がJSONではなくxmlなんですね。xmlのパース自体はやったことあるんですが、今回はちょっとうまくできなくって、RSSから取得してやることにしました〜。
JavaでのRSSの解析に際して、参考にさせて頂いたのはこちら。もうこれはほとんどこのまんまなんですが、一応例を乗っけておきます。


        List<Map<String, String>> array = new ArrayList<Map<String, String>>();
        Map<String, String> entry = null;
        
        URL url = new URL("http://b.hatena.ne.jp/a-know/rss");
        ChannelIF channel = RSSParser.parse(new ChannelBuilder(), url);
        Collection list = channel.getItems();

        ItemIF[] items = (ItemIF[]) list.toArray(new ItemIF[0]);

        for (int i = 0; i < 10; i++) {
            entry = new HashMap<String, String>();
            entry.put("target_title", items[i].getTitle());
            entry.put("target_url", items[i].getLink().toString());
            entry.put("comment", items[i].getDescription());
            entry.put("date", items[i].getDate());
            
            InputStream is = new URL("http://api.b.st-hatena.com/entry.count?url=" + URLEncoder.encode(items[i].getLink().toString(), "utf-8")).openStream();
            entry.put("hatebu_count",inputStreemToString(is));
            
            entry.put("hatebu_url", "http://b.hatena.ne.jp/entry/" + items[i].getLink().toString());
            
            array.add(entry);
        }
        
        map.put("entries", array);
        


オマケで、対象のページが何ブクマいってるかのカウントも取得してます。


大変だったけど、楽しかった!

です。
ここまでできたらブログパーツ化も・・・なんて欲も出てきたり出て来なかったり。「ブログパーツ」、純粋に興味はあるんだけど、ノウハウがないんだよなぁ。
そもそも、世間では「はてなココ」よりも「foursquare」だと思うので、イマココのブログパーツにはあまり需要はないと思うんだけど。。

ちなみに今、

Google App Engineの新しい本を読んでまして。


Google API Expertが解説する Google App Engine for Java実践ガイド

Google API Expertが解説する Google App Engine for Java実践ガイド


安心&納得の、shin1ogawa氏謹製。

これには「Google App Engineでは特に『クライアントはHTML + JavaScript』『サーバ側アプリケーションはJSONなどのデータを返すだけ、状態も持たない』といった構成で設計するのがおすすめです」とあったので(今までつくったGAEアプリはjspでゴリゴリだった)、ちょうどいい題材だってんで、今回このページもそれでやってみました〜。
結果、実際にやってみて、開発負荷とかはそう変わらないかんじだったので、これで少しでもUXの向上が図れるのなら、なるほどこの手法はいいな〜っと感じましたっ。

・・・あ、もちろん、「CSS Nite 2012 in OKAYAMA」で勉強したことも活用しましたよっ 笑