LoginSignup
3
3

More than 5 years have passed since last update.

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

Posted at

D言語でもTwitter UserStreamがしたい!

はい、というわけでやってみたって話です
ちなみに、やったのは数日前なのですがまあ、多分QiitaにもだれもD言語でUserStreamはやってなかったようなので書いてみることにしました

というわけでやってみた

はじめに免責事項としましては、OAuthの実装のところはわからなかったので
woxtu氏の 市民、認証は義務です の記事を参考に(ほぼ完全なコピーになってしまいましたが・・・)しました
ご了承ください
ネットワーク通信は std.net.curl を実装してjsonパーサーは std.json を使っています まあ、他のものはコード見てください

というわけで、コードを貼ります

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

このわかりにくいコードを解説してみる

今からOAuth認証などは先ほど貼らせて頂いた参考リンクの方を参照してください

まず最初に

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

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

次に、

auto st = byLineAsync(url ~ "?" ~ path);

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

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であることを判定します
そして、

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

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

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

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

で取り出せます、入れ子構造になってるところはobject["key2"]をもう一度つける感じで

で、多分このコードで結構謎いと思われるのが、

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

だと思うのですが、これには理由がありまして
std.net.curlを使うとcurlのログというかprogressが出てくるんですよね
で、消したかったのですが調べてみるとどうにも消せないみたいなので
無理やり---で上書きして見えなくしています
progressがあると見難くて仕方ないのでこうしました

今後これでやりたいこと

私は現在Rubyでてふてふというbotを開発しているのですが、これをD言語で描き直そうかなあと思っているんですよね

D言語でTwitter bot、めっちゃ胸熱じゃないですか!

ちょっとだけてふてふについて書くと、実はてふてふはもともと私がC言語しかできない時(プログラミングを初めてすぐの頃)に開発を始めて
それで挫折して(C言語ろくに理解しないうちにやったので・・・)しばらくしてからRubyで書きなおしたんですよね
最近、てふてふをもう一度まっさらな状態でRubyで書きなおしててふてふ2を作った(ている)わけですが
どうも、RubyでもいいけどD言語で作りたくなってさっきの結論に至ったわけです はい

ということで、今回はD言語でTwitter UserStreamを受信してみた!っていう話でした!

では、以上です

α改

3
3
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3