D言語でTwitter UserStreamを受信してみた話

#D言語でもTwitter UserStreamがしたい！
はい、というわけでやってみたって話です
ちなみに、やったのは数日前なのですがまあ、多分QiitaにもだれもD言語でUserStreamはやってなかったようなので書いてみることにしました
  
  
##というわけでやってみた
はじめに免責事項としましては、OAuthの実装のところはわからなかったので
woxtu氏の [市民、認証は義務です](http://qiita.com/woxtu/items/9656d426f424286c6571) の記事を参考に(ほぼ完全なコピーになってしまいましたが・・・)しました
ご了承ください
ネットワーク通信は `std.net.curl` を実装してjsonパーサーは `std.json` を使っています まあ、他のものはコード見てください  
というわけで、コードを貼ります  

```d:user_stream_test.d
import std.digest.sha,
       std.algorithm,
       std.datetime,
       std.net.curl,
       std.base64,
       std.array,
       std.regex,
       std.stdio,
       std.json,
       std.conv,
       std.uri;
 
ubyte[] hmac_sha1(in string key, in string message){
  auto padding(in ubyte[] k){
    auto h = (64 < k.length)? sha1Of(k): k;
    return h ~ new ubyte[64 - h.length];
  }
  const k = padding(cast(ubyte[])key);
  return sha1Of((k.map!q{cast(ubyte)(a^0x5c)}.array) ~ sha1Of((k.map!q{cast(ubyte)(a^0x36)}.array) ~ cast(ubyte[])message)).dup;
}
string signature(string cks, string ats, string method, string url, string[string] params){
   
  auto query = params.keys.sort.map!(k => k ~ "=" ~ params[k]).join("&");
  auto key = [cks, ats].map!encodeComponent.join("&");
  auto base = [method, url, query].map!encodeComponent.join("&");
   
  string oauthSignature = encodeComponent(Base64.encode(hmac_sha1(key, base)));
   
  return oauthSignature;
}
void main(){
  string url = "https://userstream.twitter.com/1.1/user.json";
  string now = Clock.currTime.toUnixTime.to!string;
  string consumerKey       = "Your ConsumerKey",
         consumerKeySecret = "Your ConsumerKeySecret",
         accessToken       = "Your AccessToken",
         accessTokenSecret = "Your AccessTokenSecret";
   
  string[string] params = [
    "oauth_consumer_key"     : consumerKey,
    "oauth_nonce"            : "4324yfe",
    "oauth_signature_method" : "HMAC-SHA1",
    "oauth_timestamp"        : now,
    "oauth_token"            : accessToken,
    "oauth_version"          : "1.0"];

  foreach(key, value; params)
    params[key] = encodeComponent(value);
  
  string oauthSignature = signature(consumerKeySecret, accessTokenSecret, "GET", url, params);
  params["oauth_signature"] = oauthSignature;
   
  auto authorizeKeys = params.keys.filter!q{a.countUntil("oauth_")==0};
  auto authorize = "OAuth " ~ authorizeKeys.map!(k => k ~ "=" ~ params[k]).join(",");
   
  string path = params.keys.map!(k => k ~ "=" ~ params[k]).join("&");
   
  auto http = HTTP();
  http.addRequestHeader("Authorization", authorize);
  http.method = HTTP.Method.get;

  auto st = byLineAsync(url ~ "?" ~ path);
  foreach(line; st){
    if(match(line.to!string, regex(r"\{.*\}"))){
      auto parsed = parseJSON(line.to!string);
      if("text" in parsed.object){//tweet
        writeln("\r------------------------------------------------------------------------------");
        writefln("%s:\n%s", parsed.object["user"].object["name"].str, parsed.object["text"].str);
        writeln("------------------------------------------------------------------------------");
      }
    }
  }
}

```

こんな感じです
これは私のGistにも上げてあります
[alphaKAI/user_stream_test.d](https://gist.github.com/alphaKAI/9585216)
  
  
##このわかりにくいコードを解説してみる
今からOAuth認証などは先ほど貼らせて頂いた参考リンクの方を参照してください
  
  
まず最初に

```d
auto http = HTTP();
http.addRequestHeader("Authorization", authorize);
http.method = HTTP.Method.get;
```

でインスタンスを取ってきます
そして、httpのヘッダに`Authorization`をつけて HTTPのメソッドを`GET`に指定します
  
次に、

```d
auto st = byLineAsync(url ~ "?" ~ path);
```
  
こうすることで、非同期でgetで受信を始めます
ちなみに、UserStreamはご存知の通り `終わらないget` みたいな感じなのでこうすることでずっとgetし続けます(切れない限りは)
そしてそれをforeachで一つ一つ取り出していきます

```d
foreach(line; st){
  if(match(line.to!string, regex(r"\{.*\}"))){
    auto parsed = parseJSON(line.to!string);
    if("text" in parsed.object){//tweet
      writeln("\r------------------------------------------------------------------------------");
      writefln("%s:\n%s", parsed.object["user"].object["name"].str, parsed.object["text"].str);
      writeln("------------------------------------------------------------------------------");
    }
  }
}
```
  
まず、なぜforeachの次に正規表現をしているのか、と言いますと
json以外の物が流れてきた場合にjsonパーサーにかけてしまうとエラーが発生するので、jsonであることを判定します
そして、

```d
auto parsed = parseJSON(line.to!string);
```

これでjsonをパースして
次に、パースしたjsonがtweetであるかを判定します
というのも、ツイート以外のイベントもあるので、エラーを回避するためにtweet判定をしています(この判定をしていなくて数時間謎の受信できないエラーに悩まされていました・・・)  

`std.json`でデータにアクセスするには

```d
string value   = parsed.object["key"].str;
string value2  = parsed.object["key"]["key2"].str;
```

で取り出せます、入れ子構造になってるところはobject["key2"]をもう一度つける感じで
  
  
で、多分このコードで結構謎いと思われるのが、

```d
writeln("\r---がたくさん続く");
```

だと思うのですが、これには理由がありまして
std.net.curlを使うとcurlのログというかprogressが出てくるんですよね
で、消したかったのですが調べてみるとどうにも消せないみたいなので
無理やり`---`で上書きして見えなくしています
progressがあると見難くて仕方ないのでこうしました  
  
  
##今後これでやりたいこと
私は現在Rubyで[てふてふ](https://twitter.com/tefutefu_tyou)というbotを開発しているのですが、これをD言語で描き直そうかなあと思っているんですよね  
D言語でTwitter bot、めっちゃ胸熱じゃないですか！  
ちょっとだけてふてふについて書くと、実はてふてふはもともと私がC言語しかできない時(プログラミングを初めてすぐの頃)に開発を始めて
それで挫折して(C言語ろくに理解しないうちにやったので・・・)しばらくしてからRubyで書きなおしたんですよね
最近、てふてふをもう一度まっさらな状態でRubyで書きなおしててふてふ2を作った(ている)わけですが
どうも、RubyでもいいけどD言語で作りたくなってさっきの結論に至ったわけです はい  
  
  
ということで、今回はD言語でTwitter UserStreamを受信してみた！っていう話でした！  
  
では、以上です  
  
  
α改