F#+TwitterizerでStreaming

.NETでTwitterAPIを使うライブラリないかなと思っていたら、Twitterizerというものがあるらしい。Streaming APIへの対応も進んでいる模様。
というわけでさっそく使ってみることにしてみた。

わりとざっくり説明を省くので下記書籍を参照のこと。

Twitter API ポケットリファレンス (POCKET REFERENCE)

Twitter API ポケットリファレンス (POCKET REFERENCE)

インストール

Visual Studio拡張機能の一つであるNuGet Package Managerを利用。
具体的にはプロジェクトを作成した後にPackage Manager Console上で

PM: Install-Package twitterizer
PM: Install-Package twitterizer-streaming

というコマンドをたたけば参照設定までやってくれる。NuGetてふ便利。
この方法だと最新の2.3.3がインストールされる模様。
ちなみに対象のフレームワーク.NET Framework 4じゃないとStreamingのほうをきちんと読み込めないんじゃないかな(要確認)。
あとClient ProfileにはSystem.Webがないのでそっちでは使えない、念のため。

UserStreamを利用してみる

とりあえず、自分のタイムラインをコンソール上にごりごり表示してみよう。

open System
open Twitterizer
open Twitterizer.Streaming

let userAgent = "FsStreamViewer"
let token = new OAuthTokens()

let settingToken (token:OAuthTokens)  =
  token.set_ConsumerKey("**")
  token.set_ConsumerSecret("**")
  token.set_AccessToken("**")
  token.set_AccessTokenSecret("**")

let startView token =

  settingToken token

  let opts = new StreamOptions()

  use stream = new TwitterStream(token, userAgent, opts)
  let disconnected = ref false
  let stCreatedCallback = new StatusCreatedCallback (fun stat -> Console.WriteLine stat.Text )
  let stStreamStoppedCallback = new StreamStoppedCallback (fun _ -> disconnected := true)

  stream.StartUserStream(null, stStreamStoppedCallback, stCreatedCallback, null, null, null) |> ignore

  while !disconnected = false do ()

[<EntryPoint>]
let main _ = 
  startView token
  0

OAuthTokensの各種プロパティがF#からだと見えないのでset_Hogehogeメソッドで設定。
2.3.3からTwitterStream.StartUserStreamにデリゲートを渡す方式になったみたい。それ以前のバージョンではイベントを登録する方式だったはず。まぁ、引数7個は面倒です…ということで今回は最低限のものだけ突っ込むことに。それにしてもやけにヌルヌルしてる。
実行してみるとコンソール上に自分のタイムラインが順次表示されていくと思う。

trackを指定する

なんとなくtrackを指定してみることにする。

(* 略 *)

let rec addTracks (opts:StreamOptions) = 
  function
  | x::xs -> 
    opts.Track.Add x
    addTrack opts xs
  | [] -> ()

let startView token trackList =

  settingToken token

  let opts = new StreamOptions()
  addTracks opts trackList

(* 略 *)

[<EntryPoint>]
let main _ = 
  startView token ["test"]
  0

addTracksにキーワードのリストを渡してまとめて登録。
実行して自分のタイムライン以外にもtrackで指定した単語を含むツイートが表示されていれば成功。

PublicStreamを表示させたい

こうなるとStreamed Tweetのひとつ、filterを使ってpublicなツイートを取得したくなるよね。ハッシュタグとか。
というわけでStartUserSreamをStartPublicStreamに差し替える…だけで済むなら話は早かったのだが…。

Streaming API turning SSL only on September 29th | Twitter Blogs
要するにhttpではなくhttps使ってねと。
それでですね、Twitterizerさんはまだ対応されていないのです。しかもハードコーディングされているみたいなのでどうしようもない。しかも2.3.3はまだソースコードが公開されていないぽい。
仕方がないのでバージョンがちょっと違うけど

の200行〜を眺めてみる。
短い。これなら自力で同様の関数を作れるのでは?
さっそくやってみましょう。

let startMyPublicStream (stream:TwitterStream) stopped created =
  
  let setFieldData name data =
    let field = stream.GetType().GetField(name, BindingFlags.Instance ||| BindingFlags.NonPublic)
    field.SetValue(stream, data)

  let builder = new WebRequestBuilder(new Uri("https://stream.twitter.com/1/statuses/filter.json"), HTTPVerb.POST, stream.Tokens, true, userAgent)
  
  let prepareStreamOptions = stream.GetType().GetMethod("PrepareStreamOptions", BindingFlags.Instance ||| BindingFlags.NonPublic)
  prepareStreamOptions.Invoke(stream, [|builder|]) |> ignore
  
  let request = builder.PrepareRequest()

  setFieldData "streamStoppedCallback" stopped
  setFieldData "statusCreatedCallback" created
  setFieldData "stopReceived" false

  let StreamCallback = stream.GetType().GetMethod("StreamCallback", BindingFlags.Instance ||| BindingFlags.NonPublic)  
  let callback = new AsyncCallback (fun result -> StreamCallback.Invoke(stream, [|result|]) |> ignore)
  
  request.BeginGetResponse(callback, request) 

スーパーリフレクションタイム。デリゲート登録部分は今回は使うやつ以外はしょってます。
これにあわせてstartViewやmainをちょこちょこ変更して実行。表示されれば問題なし(と思うことにする)。

ソースコード

open System
open System.Net
open System.Reflection
open Twitterizer
open Twitterizer.Streaming

let userAgent = "FsSreamViewer"
let token = new OAuthTokens()

let settingToken (token:OAuthTokens)  =
  token.set_ConsumerKey("**")
  token.set_ConsumerSecret("**")
  token.set_AccessToken("**")
  token.set_AccessTokenSecret("**")

let startMyPublicStream (stream:TwitterStream) stopped created =
  
  let setFieldData name data =
    let field = stream.GetType().GetField(name, BindingFlags.Instance ||| BindingFlags.NonPublic)
    field.SetValue(stream, data)

  let builder = new WebRequestBuilder(new Uri("https://stream.twitter.com/1/statuses/filter.json"), HTTPVerb.POST, stream.Tokens, true, userAgent)
  
  let prepareStreamOptions = stream.GetType().GetMethod("PrepareStreamOptions", BindingFlags.Instance ||| BindingFlags.NonPublic)
  prepareStreamOptions.Invoke(stream, [|builder|]) |> ignore
  
  let request = builder.PrepareRequest()

  setFieldData "streamStoppedCallback" stopped
  setFieldData "statusCreatedCallback" created
  setFieldData "stopReceived" false

  let StreamCallback = stream.GetType().GetMethod("StreamCallback", BindingFlags.Instance ||| BindingFlags.NonPublic)  
  let callback = new AsyncCallback (fun result -> StreamCallback.Invoke(stream, [|result|]) |> ignore)
  
  request.BeginGetResponse(callback, request) 

let rec addTracks (opts:StreamOptions) = 
  function
  | x::xs -> 
    opts.Track.Add x
    addTracks opts xs
  | [] -> ()

let startView token streamMode trackList =

  settingToken token

  let opts = new StreamOptions()
  addTracks opts trackList

  use stream = new TwitterStream(token, userAgent, opts)
  let disconnected = ref false
  let stCreatedCallback = new StatusCreatedCallback (fun stat -> Console.WriteLine stat.Text )
  let stStreamStoppedCallback = new StreamStoppedCallback (fun _ -> disconnected := true)

  streamMode stream stStreamStoppedCallback stCreatedCallback |> ignore

  while !disconnected = false do ()
    
(*
Streaming APIがSSLのみ対応となったためtwitterizer.Streaming.TwitterStream.StartPublicStream内でエラーが発生する 
 let publicStream (stream:TwitterStream) stopped created = stream.StartPublicStream(stopped, created, null, null)
*)

(* リフレクション多用による自作StartPublicStream *)
let publicStream stream stopped created = startMyPublicStream stream stopped created

let userStream (stream:TwitterStream) stopped created = stream.StartUserStream(null, stopped, created, null, null, null, null)

[<EntryPoint>]
let main _ = 
  startView token publicStream ["test"]
  0

結論

早く修正されないかな。
まぁでも、F#F#できたので個人的には満足でふ。
というか寝不足でまた命名規則が怪しいなぁ…