YouTubeLiveの配信コメントをUnityで利用する

![スクリーンショット 2018-02-12 13.21.12.png](https://qiita-image-store.s3.amazonaws.com/0/77528/abc882fc-ffe2-7fc3-91aa-b1e10d7b59f0.png)

YouTube Liveのチャットコメントを使って遊んでみます。

[チャンネルIDから辿る](https://qiita.com/platoronical/items/ba99be66d0cfb85e6038)方法をとらずに、Unity上でOAuth認証を行います。

こちらのほぼUnity実装版に相当
https://blog.sky-net.pw/article/86

# APIキーの用意

[Google APIs](https://console.developers.google.com/apis)に行き

* プロジェクトを作成し`YouTube Data API v3`を有効化

次にOAuthクライアントを作成します。

* 認証情報＞「OAuth 同意画面」 に適当なサービス名を入力
* 認証情報>「認証情報を作成」>「OAuth クライアント ID」から作成（アプリケーションの種類はその他、名前は適当）

これで`client_id`と`client_secert`が得られます。

# 認可コードの取得

ここからUnity。

コード全体は[こちら](https://gist.github.com/n0mimono/06d3c75decff15b7c788fce9cf2a40ec)。

```c#
public class YouTubeLiveController : MonoBehaviour { 
  
  IEnumerator Start() {
    var clientId = "client_id";
    var clientSecret = "client_secret";

    var code = "";
    LocalServer (c => code = c);

    var authUrl = "https://accounts.google.com/o/oauth2/v2/auth?response_type=code"
      + "&client_id=" + clientId
      + "&redirect_uri=" + "http://localhost:8080"
      + "&scope=" + "https://www.googleapis.com/auth/youtube.readonly"
      + "&access_type=" + "offline";
    Application.OpenURL (authUrl);
    yield return new WaitUntil (() => code != "");

    Debug.Log (code);
  }

  void LocalServer(Action<string> onReceive) {
    ThreadStart start = () => {
      try {
        var listener = new HttpListener();
        listener.Prefixes.Add("http://*:8080/");
        listener.Start();

        var context = listener.GetContext();
        var req = context.Request;
        var res = context.Response;

        var re = new Regex (@"/\?code=(?<c>.*)");
        var code = re.Match (req.RawUrl).Groups ["c"].ToString();
        onReceive(code);

        res.StatusCode = 200;
        res.Close();
      } catch (Exception e) {
        Debug.LogError(e);
      }
    };
    new Thread (start).Start ();
  }

}
```

`client_id`からコード取得用のURLを構築して`Application.OpenURL`でブラウザを開きます。

ブラウザで認証後にUnity側で認証結果を受け取る手段がないため、ローカルサーバを立ててこちらにリダイレクトさせて無理やり`code`を取得します。

# アクセストークンの取得

取得した`code`を使用して`access_token`を取得します。

```c#
  IEnumerator Start() {
    // snip

    var tokenUrl = "https://www.googleapis.com/oauth2/v4/token";
    var content = new Dictionary<string,string> () {
      { "code", code },
      { "client_id", clientId },
      { "client_secret", clientSecret },
      { "redirect_uri",  "http://localhost:8080" },
      { "grant_type", "authorization_code" },
      { "access_type", "offline" },
    };
    var request = UnityWebRequest.Post (tokenUrl, content);
    yield return request.SendWebRequest();

    var json = JSON.Parse (request.downloadHandler.text);
    var token = json["access_token"].RawString();

    Debug.Log (token);
  }
```

実際のレスポンスは次のようになっています。リフレッシュトークンも取得できますがここではとりあえず使用しません。

```
{
 "access_token": "ya29.GltgB...",
 "token_type": "Bearer",
 "expires_in": 3600,
 "refresh_token": "1/hsjPU..."
}
```

JSONパーサーとして[SimpleJson](http://wiki.unity3d.com/index.php/SimpleJSON)を利用します。。`RawString()`の中身は

```c#
public static class SimpleJsonUtility {
  public static string RawString(this JSONNode node) {
    var len = node.ToString ().Length - 2;
    return node.ToString ().Substring (1, len);
  }
}
```

パース結果を`json["access_token"].ToString()`すると`""abcdef""`のようにダブルクオーテーションまで文字列に含まれてしまうのでその対応をしています。

# チャットIDの取得

[YouTube Live Streaming API](https://developers.google.com/youtube/v3/live/docs/liveBroadcasts)を使っていきます。

はじめに`liveChatId`を取得します。

```c#
  IEnumerator Start() {
    // snip

    var url = "https://www.googleapis.com/youtube/v3/liveBroadcasts?part=snippet";
    url += "&id=" + "xxxxxxxxxxx";

    var req = UnityWebRequest.Get (url);
    req.SetRequestHeader ("Authorization", "Bearer " + token);
    yield return req.SendWebRequest();

    json = JSON.Parse (req.downloadHandler.text);
    var chatId = json["items"][0]["snippet"]["liveChatId"].RawString();

    Debug.Log (chatId);
  }
```

`id`は`https://www.youtube.com/watch?v=xxxxxxxxxxx`の`xxxxxxxxxxx`の部分を入れます。

対象にしている動画がライブ中でない（かそもそもライブでない）場合は`liveChatId`がレスポンスの中に含まれません。

また、トークンを指定せずにアクセスすると

```
{
 "error": {
  "errors": [
   {
    "domain": "global",
    "reason": "required",
    "message": "Login Required",
    "locationType": "header",
    "location": "Authorization"
   }
  ],
  "code": 401,
  "message": "Login Required"
 }
}
```

このように怒られます。

# コメントの取得

対象のチャットからコメントを取得します。

```c#
  IEnumerator Start() {
    // snip

    url = "https://www.googleapis.com/youtube/v3/liveChat/messages?part=snippet,authorDetails";
    url += "&liveChatId=" + chatId;

    req = UnityWebRequest.Get (url);
    req.SetRequestHeader ("Authorization", "Bearer " + token);
    yield return req.SendWebRequest();

    json = JSON.Parse (req.downloadHandler.text);
    var items = json["items"];

    foreach (var item in items) {
      var snip = item.Value ["snippet"];
      var author = item.Value["authorDetails"];
      Debug.Log (author ["displayName"].RawString () + ": "
        + snip ["displayMessage"].RawString());
    }
    Debug.Log (json["nextPageToken"]);
  }
```

コメントだけでなくコメント投稿者の名前やアイコンを取得することもできます。

何度もコメントを取得するような場合、`page_token`を指定することで前回取得分からの差分のみを取得できます。

# コメントの利用

使いやすく書き直したもの
https://gist.github.com/n0mimono/00f8e68a8fa5e2f9b36a4fab30adcd22

コメントを取得してキャラを走らせてみます。

```c#
public class Rotater : MonoBehaviour {
  [SerializeField]
  Transform character;

  [SerializeField]
  YouTubeLive.YtlController ctrl;

  float tgt;
  float speed;

  void Start() {
    Application.runInBackground = true;

    ctrl.OnMessage += msg => {
      tgt += msg.text.Length * 5f;
    };
  }

  void Update() {
    speed = Mathf.Lerp (tgt, speed, Time.deltaTime);

    transform.Rotate (Vector3.up, speed * Time.deltaTime);

    var animator = character.GetComponent<Animator> ();
    animator.SetFloat ("Speed", speed);
  }
}
```

結果

![wak2.gif](https://qiita-image-store.s3.amazonaws.com/0/77528/b1eecd65-1e21-cc15-39d5-30fc86a37298.gif)

チャットに書き込むとキャラが加速します。