DartでTwitterのStreaming APIに接続するのはボクにはまだ少し早かったようだ
概要
Dartでupdate_nameを実装する。そのためにはTwitterのStreaming APIに接続することが不可避だ。
しかし、Dartの道はオレが考えていたよりもずっと険しいものだった。果たしてオレは辿り着くことができるのだろうか。
オレはようやくのぼりはじめたばかりだからな
このはてしなく遠いDart坂をよ‥
外観
1. とりあえずDartでTwitterの認証をする。
とりあえず認証しないことには話が進みません。(いやまあ、APIキーの管理画面からアクセストークン取ってくればいいんだけどさ‥。そういうことじゃないだろう?)
TwitterはOAuthというものを使って認証をすることになっているのですが‥‥、まあそういうことの説明は面倒くさいので、WikipediaのOAuthでも参照してください。
で、このOAuthをDartでどうやって処理するかなんですが、これもまた面倒くさいので既存のライブラリを使いたいと思います。oauthという奴です。実際のところを言えばDartで自力でOAuthを実装したこともあるのですが、車軸を再発明しただけだったので、ひっそりとボクのハードディスクの隅にしまっておきたいと思います。
ともかく、Dartのプロジェクトを作り始めましょう。
$ mkdir twitter_api && cd $_
と、適当なディレクトリを掘って、そこをプロジェクトのルートということにします。
そこで、pubspec.yaml
というファイルを用意します。
name: twitter_api version: 0.1.0 description: Twitter for Dart. author: TSUYUSATO Kitsune <make.just.on@gmail.com> dependencies: oauth: '>=0.2.0 <0.3.0'
dependencies
以下が依存関係を記述していることは火を見るよりも明らかでしょう。
この状態で、
$ pub get
というコマンドを実行します。
すると依存関係にあるライブラリが自動的にpackages
というディレクトリにインストールされます。
pubで管理している場合、基本的にDartのソースコードはlib
というディレクトリ以下に書いていくことになります。しかし今回はコマンドラインから実行する前提のプログラムを作るつもりなので、bin
ディレクトリ以下に書いていくと良いと思います。
bin/authorize.dart
library update_name.authorize; import 'dart:async'; import 'dart:io'; import 'dart:convert' show JSON; import 'package:oauth/oauth.dart' as oauth; Future get async => new Future.delayed(const Duration(milliseconds: 0), () => null); Future<Map> readConsumer(String filename) => new File(filename) .readAsString() .then((String str) { var json = JSON.decode(str); return async.then((_) => {"consumer": new oauth.Token(json["key"], json["secret"])}); }); Future<Map> getRequestToken(Map state) { var compl = new Completer(); var client = new oauth.Client(state["consumer"]); client.post("https://api.twitter.com/oauth/request_token", body: {"oauth_callback": "oob"}) .then((res) { if (res.statusCode == 200 || res.statusCode == 201) { var q = Uri.splitQueryString(res.body); state["request"] = new oauth.Token(q["oauth_token"], q["oauth_token_secret"]); compl.complete(state); } else { compl.completeException(res); } }); return compl.future; } Future<Map> readPin(Map state) { stdout.writeln("Open in browser: https://api.twitter.com/oauth/authorize?oauth_token=" + state["request"].key); return async.then((_) { stdout.write("PIN> "); return stdin.readLineSync(); }).then((String line) { state["pin"] = line; return state; }); } dynamic getAccessToken (String filename) => (Map state) { var compl = new Completer(); var client = new oauth.Client(state["consumer"]); client.userToken = state["request"]; client.post("https://api.twitter.com/oauth/access_token", body: {"oauth_verifier": state["pin"]}) .then((res) { if (res.statusCode == 200 || res.statusCode == 201) { var q = Uri.splitQueryString(res.body); var access = {"key": q["oauth_token"], "secret": q["oauth_token_secret"]}; new File(filename).writeAsString(JSON.encode(access)).then(compl.complete); } else { compl.completeException(res); } }); return compl.future; }; void main(List<String> args) { readConsumer(args[0]) .then(getRequestToken) .then(readPin) .then(getAccessToken(args[1])); }
(はてなブログがDartのハイライトに対応してないっぽいのでJavaで代用したけど悪くない)
これを実行するには、
{ "key": "あなたなのコンシューマーキー", "secret": "あなたのコンシューマーシークレット" }
という構造のjsonを用意して、
$ pub run authorize <コンシューマーキーの書いたjsonのファイル名> <アクセストークンを格納したいファイル名>
という風にします。例として、consumerkey.json
にコンシューマーキーを保存しておいて、make_now_just.json
というファイルにアクセストークンを保存したいとすると、
$ pub run authorize consumerkey.json make_now_just.json Open in browser: https://api.twitter.com/oauth/authorize?oauth_token=xxxxxxxxxxxxxxxxxxxxxx PIN>
と入力待ちになります。ここで表示されたURLにブラウザでアクセスすると、
というよく見る認証画面が表示され、認証した先で表示されたPINを打ち込むと、認証されてアクセストークンが発行されます。
このアクセストークンがあれば、自由にTwitter APIを叩くことができるはずです。
2. REST APIを叩く。
というわけでREST APIを叩きます。REST APIというのは、ツイートしたりとかフォロワーの一覧を取得する類のAPIです、本当は違うけど。
色々と説明するのが面倒だし、そもそも説明したところで結局ソースコードを読むことになるだろうので解説はしません。ソースコード貼ります。
bin/tweet
library update_name.tweet; import 'dart:async'; import 'dart:io'; import 'dart:convert' show JSON; import 'package:oauth/oauth.dart' as oauth; Future<oauth.Token> loadToken(String filename) { return new File(filename).readAsString() .then((String str) { var json = JSON.decode(str); return new oauth.Token(json["key"], json["secret"]); }); } Future<Map> postAPI(String url, oauth.Token consumer, oauth.Token user, Map body) { var compl = new Completer(); var client = new oauth.Client(consumer); client.userToken = user; client.post(url, body: body) .then((res) { if (res.statusCode == 200 || res.statusCode == 201) { compl.complete(JSON.decode(res.body)); } else { compl.completeError(res); } }); return compl.future; } void main(List<String> args) { oauth.Token consumer, user; var loadConsumer = loadToken(args[0]).then((t) => consumer = t); var loadUser = loadToken(args[1]).then((t) => user = t); Future.wait([loadConsumer, loadUser]) .then((_) { stdout.write('tweet> '); var status = stdin.readLineSync(); return postAPI("https://api.twitter.com/1.1/statuses/update.json", consumer, user, {"status": status}); }) .then((json) => print(json)) .catchError((res) { print(res); print("${res.body}"); }); }
さっきと比べるとちょっと短かめのソースな気がします。
これもさっきと同じように、pub run
で実行できるのですが、Dartの入力は実に貧弱でストレスが溜まるので、rlwrap
を使って入力を快適にしつつ実行します。
$ sudo apt-get install rlwrap $ rlwrap pub run tweet consumerkey.json make_now_just.json tweet> Dartでだーっと呟いてみたぜー!
で、こうなりました。
Dartでだーっと呟いてみたぜー!
— 七枷さっき作った(CV:佐本二厘) (@make_now_just) July 22, 2014
楽しい✌('ω'✌ )三✌('ω')✌三( ✌'ω')✌
3. Streaming APIを叩く叩きたかった‥。
それじゃStreaming APIだっ!
といきたいところなんだけど‥‥。
色々と試行錯誤してるんだけどまだ上手くいってない。なぜかError 401 Unauthorized twitter user stream
とか言われてHTMLが返ってくる。わけがわからないよ。
がんばっても上手くいきそうにないし諦めも肝心かな、と思ったのでこうして記事を書いているわけです。ここまでの内容で十分役に立つ人もいるかもしれませんし。
もしこの記事を読んで(読んでなくてもいいけど)何かひらめいてStreaming APIを叩くことに成功した人がいたら、やり方を教えてください。ボクが喜びます。