セカイ内存在証明

それは多分、単なる思い付き

DartでTwitterのStreaming APIに接続するのはボクにはまだ少し早かったようだ

概要

Dartupdate_nameを実装する。そのためにはTwitterStreaming APIに接続することが不可避だ。

しかし、Dartの道はオレが考えていたよりもずっと険しいものだった。果たしてオレは辿り着くことができるのだろうか。

オレはようやくのぼりはじめたばかりだからな
このはてしなく遠いDartをよ‥

外観

  1. とりあえずDartTwitterの認証をする。
  2. REST APIを叩く。
  3. Streaming API叩く叩きたかった‥。

1. とりあえずDartTwitterの認証をする。

とりあえず認証しないことには話が進みません。(いやまあ、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というディレクトリにインストールされます。

それではDartソースコードを書いていきましょう。

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にブラウザでアクセスすると、

f:id:makenowjust:20140722230611p:plain

というよく見る認証画面が表示され、認証した先で表示された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でだーっと呟いてみたぜー!

で、こうなりました。

楽しい✌('ω'✌ )三✌('ω')✌三( ✌'ω')✌

3. Streaming API叩く叩きたかった‥。

それじゃStreaming APIだっ!

といきたいところなんだけど‥‥。

色々と試行錯誤してるんだけどまだ上手くいってない。なぜかError 401 Unauthorized twitter user streamとか言われてHTMLが返ってくる。わけがわからないよ。

がんばっても上手くいきそうにないし諦めも肝心かな、と思ったのでこうして記事を書いているわけです。ここまでの内容で十分役に立つ人もいるかもしれませんし。

もしこの記事を読んで(読んでなくてもいいけど)何かひらめいてStreaming APIを叩くことに成功した人がいたら、やり方を教えてください。ボクが喜びます。

まとめ

  • DartTwitter APIを叩くことはできる。
  • けど何だかとても面倒。
  • 誰かライブラリ作って。
  • id:sh4869氏任せた。

追記

GitHubリポジトリ作りました。

MakeNowJust/twitter_dart - GitHub