CoreTweetでPIN番号を手入力せずに認証

お久しぶりです．N.Mです．最近は趣味でTwitter APIを使ったGUIアプリケーションをC#で作ってます（C#だとGUIアプリケーションが作りやすいと思ったので）．今後不定期で，その制作過程で大変だったことを発信していければと思います．今回はその第1弾．
（次回は[ウィンドウサイズの調整について](https://qiita.com/HexagramNM/items/8d7633c8c2f340402ba2)）

今はこんな感じの，直近のツイートから誰がTL上にいるのかをリストアップするアプリを作っています．
（アプリケーション自体は，[ここ](http://nmsgameproject.web.fc2.com/)で配布しております．今後も更新予定です！）
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
<img width="346" alt="TLMem.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/152791/39bbb4a0-285a-043d-a991-6cda60f2b5ff.png">

# CoreTweetの認証
Twitter APIについてはC#のライブラリ"CoreTweet"を利用していまして，[ここ](https://github.com/CoreTweet/CoreTweet/wiki/%E8%AA%8D%E8%A8%BC)にも認証の仕方が書いてあるのですが，そのアカウントでアプリを使うことを認証した後に，7桁のPINコードが表示されます．APIをつかうために，そのPINコードを手動で入力するか，HTML解析するかして`OAuth.GetTokens`に渡す必要があります．手動で入力する方法はたくさん情報が出てきたので，最初はそれを採用してましたが，使ってるうちに手入力が面倒になったので，今回 **HTML解析による取得** に挑戦しました．

# 環境・前提
- Visual Studio 2017で開発
- Twitter APIの利用のためにCoreTweetを使用
- WPFアプリケーションを制作（Formsではない）

# 仕組み
認証ページは最初のURLは`https://api.twitter.com/oauth/authorize`の後にいろいろ文字列がついた状態になっています．そして，認証してもしなくても，最終的にURLが`https://api.twitter.com/oauth/authorize`になります．認証した場合はHTML内に

```html
<code>7桁のPINコード</code>
```

が埋め込まれています（そして`<code>`タグはページのこの部分にしか出てきません）．なので，やるべきこととしては

1. 認証ページを表示して，ユーザに認証させる
2. ページのリンクが`https://api.twitter.com/oauth/authorize`になったら，ページのHTMLコードを取得する．
3. HTMLソースから`<code>`の文字列を検索し，あれば後ろの7桁のPINコードを取り出し，認証する（なければ認証失敗のエラーを出す）．

となります．

# 1. 認証ページを表示させる
CoreTweetの日本語Wikiだと`System.Diagnostics.Process.Start`で認証ページを表示していますが，HTMLソースを抜き取るのが難しそうだったので，認証ページの表示はWPFアプリケーションに`WebBrowser`のコンポーネントを埋め込むことにしました（'twitterWeb'と命名しました）．で，`WebBrowser`の`Navigate`メソッドで認証ページのURLに飛べば表示できます．

<img width="966" alt="TLMemAuth.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/152791/f1d90326-fd15-319e-1e77-3841239d601c.png">
こんな感じ．ここから，ユーザが認証したら，すぐにAPIが使える状態にしていきます．

# 2. ページのURIの取得，HTMLコードの取得

ここでいろいろつまづきました．つまづいたポイントとしては

- WebBrowserがnullで参照できなかった．
- WPF（`System.Windows.Controls`
）ではなく，Formsのほう（`System.Windows.Forms`）の情報ばかり出て混乱した．
- WPFのWebBrowserからHTMLを抜き取る方法が分からなかった．

の3点ですね．

まず，1点目の参照の問題は，`Navigate`メソッドを呼び出したあとに，`WebBrowser`のURLを確認しようとしたんですけど，そのタイミングだと，まだ処理が完了してないのでnullが返ってきてしまうそうです．なので，`WebBrowser`の処理が完了した際に呼ばれるイベントハンドラ`LoadCompleted`で，もろもろの処理を行います．

```C#
twitterWeb.Navigate(session.AuthorizeUri);
LoadCompletedEventHandler twitterWebUpdate = null;

twitterWebUpdate = (tSender, e) =>
{
    string pin = "";
    string finishedUrl = @"https://api.twitter.com/oauth/authorize";
    string url = twitterWeb.Source.AbsoluteUri;
    if (url == finishedUrl)
    {
        //ここにHTMLソースの取得とPINコードの抽出処理を実装                
    }                
};
twitterWeb.LoadCompleted += twitterWebUpdate;
```

`finishedURL`に認証後に行くはずのページのURLを，`URL`に`twitterWeb`の現在のページのURLを格納しておき，この2つが一致したら認証後のページに行ったと判断します．

`WebBrowser`からのHTMLの取得ですが，Visual StudioのプロジェクトでCOMのmshtmlを参照するようにして，`mshtml.IHTMLDocument2`を介して，取得するようです（[参考](https://uchukamen.wordpress.com/2011/01/09/webbrowser-%E3%81%A7%E3%80%81inntertext%E3%82%84%E3%83%AA%E3%83%B3%E3%82%AF%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95/)）．

HTML抽出部分を加えるとこんな感じ

```C#
twitterWeb.Navigate(session.AuthorizeUri);
LoadCompletedEventHandler twitterWebUpdate = null;

twitterWebUpdate = (tSender, e) =>
{
    string pin = "";
    string finishedUrl = @"https://api.twitter.com/oauth/authorize";
    string url = twitterWeb.Source.AbsoluteUri;
    if (url == finishedUrl)
    {
         mshtml.IHTMLDocument2 doc = (mshtml.IHTMLDocument2)twitterWeb.Document;
         string html = doc.body.innerHTML;
        //ここにPINコードの抽出処理を実装                
    }                
};
twitterWeb.LoadCompleted += twitterWebUpdate;
```

# 3. PINコードの抽出
ここまでできれば，あとは`html`から`<code>`の文字列を`IndexOf`メソッドで検索して，
パパパっと`Substring`メソッドでPINコードの7桁の数字を抽出して終わり！

```C#
twitterWeb.Navigate(session.AuthorizeUri);
LoadCompletedEventHandler twitterWebUpdate = null;

twitterWebUpdate = (tSender, e) =>
{
    string pin = "";
    string finishedUrl = @"https://api.twitter.com/oauth/authorize";
    string url = twitterWeb.Source.AbsoluteUri;
    if (url == finishedUrl)
    {
         mshtml.IHTMLDocument2 doc = (mshtml.IHTMLDocument2)twitterWeb.Document;
         string html = doc.body.innerHTML;
         int codeIndex = html.IndexOf("<code>");
         if (codeIndex >= 0)
         {
             //<code>があった=認証に成功した場合
             pin = html.Substring(codeIndex + 6, 7);
             t = OAuth.GetTokens(session, pin);
         }
         else
         {
             //<code>がなかった=認証に失敗した場合
             MessageBox.Show("認証に失敗しました．");
         }                
    }                
};
twitterWeb.LoadCompleted += twitterWebUpdate;
```

これで`Tokens`変数 `t`を介して，Twitter APIをいじることができます．

# 補足
- 認証ページにはほかにもリンクがありましたが，そこに飛ぼうとすると新しいタブで開かれるためか，Internet Explorerが新規で実行されます．`WebBrower`側のページは認証のリンク以外では飛ばなかったので，この仕組みでもうまくいっているようです．