OBSを用いた生放送でスプラトゥーン2の直近戦績を自動表示するプログラムを書いた話【C#,HTML】

# OBSを用いた生放送でスプラトゥーン2の直近戦績を自動表示するプログラムを書いた話【C#,HTML】

## 概要

ニコニコにてたまにスプラトゥーン生放送をしてます。

コケいろがなんかするコミュニティ-ニコニコミュニティ
https://com.nicovideo.jp/community/co1292012

(最近放送してねーなコイツ。。。)

放送画面に直近10戦の勝敗とキルデス数を表示しています。

![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93212/d0771be1-b040-4a07-6b4f-efc8c7dc3374.png)

(右下の開いてるスペースにはニコ生のコメントが反映されます)

- 連勝しているときイキりたい
- 連敗してるときは試合止めて反省したい
- リスナーとのコミュニケーションに役立てたい
- 連勝しているときイキりたい
- 連勝しているときイキりたい
- 連勝しているときイキりたい

と思い、プログラムを自作して実現しました。

戦績を自動取得し、放送画面UIに自動反映・更新しています。


この機能を実現するためにやってることは大きく分けて下記２つです。

- スプラトゥーン2の公式スマホアプリ「イカリング2」の非公開APIを利用して戦績を取得する
- 取得した戦績をHTMLに書き出し、それを一定時間ごとにリロードしてOBSで表示する


それぞれの実装方法について紹介します。


## 直近戦績を取得する

先述の通り、スプラトゥーン2の公式スマホアプリ「イカリング2」の非公開APIを使用して戦績を取得します。

この非公開APIを利用するためにはCookieに含まれる「iksm_session」というセッションキーが必要です。

「iksm_session」は通信をキャプチャして取得することになりますが、イカリング2の度重なるアップデートにより取得するための手段に対して制限が厳しくなっています。

取得方法について記述したとしてもすぐ情報が古くなってしまうだろうと考えているため、この記事では記述しません。

私はsplatnet2statinkのWikiを参考にNoxを用いて取得しました。

https://github.com/frozenpandaman/splatnet2statink/wiki/mitmproxy-instructions

英語になってしまいますが、最新バージョンの情報までまとめられてて大変助けられてます。

ちなみにこのsplatnet2statinkは戦績を自動取得し、[stat.ink](https://stat.ink/)という戦績記録サービスにデータを送信するpythonプログラムです。


で、「iksm_session」を取得したらCookieに食わせてやることで非公開APIを利用できます。

非公開APIなのでドキュメントありません。雰囲気で使う必要があります。

APIのエンドポイントは「iksm_session」取得過程の通信キャプチャで何となく分かるほか、splat2statinkを参考にすることであたりを付けることができます。

それらを参考に、C#向けに自作クライアントを作りました。

https://github.com/kokeiro001/IksmClientDotNet

今回のプログラムを実装するに当たり必要なAPI対応しかしていないため、叩けるAPIは非常に限定的です。

こんな感じのリクエストパラメータ設定、エンドポイントでAPIが利用できます。

```c#
// https://github.com/kokeiro001/IksmClientDotNet/blob/e06214ed0a1f0f13112b731693e35c0bb6c14279/IksmClientDotNet.Core/Services/IksmClient.cs

public async Task<string> Request(string requestUrl)
{
    using (var handler = new HttpClientHandler()
    {
        UseCookies = false,
        AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
    })
    using (var client = new HttpClient(handler))
    using (var request = new HttpRequestMessage(HttpMethod.Get, $"https://app.splatoon2.nintendo.net/{requestUrl}"))
    {
        request.Headers.Add("User-Agent", "Mozilla/5.0 (Linux; Android 5.1.1; SM-N950F Build/NMF26X) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/39.0.0.0 Mobile Safari/537.36");

        request.Headers.Host = "app.splatoon2.nintendo.net";
        request.Headers.Add("x-unique-id", "32449507786579989234");
        request.Headers.Add("x-requested-with", "XMLHttpRequest");
        request.Headers.Add("x-timezone-offset", "-540");
        request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("*/*"));
        request.Headers.Referrer = new Uri("https://app.splatoon2.nintendo.net/home");
        request.Headers.AcceptEncoding.Add(StringWithQualityHeaderValue.Parse("gzip"));
        request.Headers.AcceptEncoding.Add(StringWithQualityHeaderValue.Parse("deflate"));
        request.Headers.AcceptLanguage.Add(StringWithQualityHeaderValue.Parse("ja-JP"));

        request.Headers.Add("Cookie", $"iksm_session={iksmSession};");

        var response = await client.SendAsync(request);

        return await response.Content.ReadAsStringAsync();
    }
}

public async Task<ResultRoot> GetBattleResults()
{
    var result = await Request("api/results");
    return JsonConvert.DeserializeObject<ResultRoot>(result, jsonSerializerSettings);
}

public async Task<string> GetBattleResultDetail(int battleId)
{
    return await Request($"api/results/{battleId}");
}
```

`GetBattleResults`メソッドや`GetBattleResultDetail`メソッドを用いることでで勝敗やキルデス数、スペシャル利用数やカウントの進み具合と行った戦績に関するデータが取得できます。

戦績良かったときはニヤニヤできます。


## OBSに戦績を表示する

次はこれらを配信ソフトであるOBSに反映していきます。

OBSでは入力ソースにBrouserSourceがあります。

これはWebのURLを指定してやることでそこを表示したり、ローカルのHTMLパスを指定してやることでそれを表示したりできます。

OBSにはテキストソースもあるのですが、フォントやレイアウトについて調整したくなると辛みがあります。

ので、今回はCSS使って簡単に見た目整えられるBrowserSourceを採用しました。


で、先程取得してきた戦績情報を下記のようなHTMLとして出力し、そいつを読み込ませています。

そんなに長くならんので、出力したHTMLとcss両方載せます。

```html
<!-- BattleResult.html -->
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>直近の戦績</title>
		<link rel="stylesheet" href="BattleResult.css" type="text/css">
	    <!-- 3秒ごとに自身をリロードする！ -->
		<script type="text/JavaScript">setTimeout("location.href = 'BattleResult.html';", 3000);</script>
	</head>
	<body>
		<span>直近の戦績</span>
		<br>
		<span class="note">(5分毎に自動更新)</span>
		<br>
		<div>
			<!-- insert recent battle data -->
			<span class="win">勝 13( 4)k/10d</span>
			<br>
			<span class="win">勝  6( 1)k/ 0d</span>
			<br>
			<span class="lose">負  2( 0)k/ 6d</span>
			<br>
			<span class="win">勝  4( 0)k/ 7d</span>
			<br>
			<span class="lose">負 14( 5)k/12d</span>
			<br>
			<span class="win">勝  4( 0)k/ 2d</span>
			<br>
			<span class="win">勝 14( 5)k/ 6d</span>
			<br>
			<span class="lose">負  2( 1)k/ 3d</span>
			<br>
			<span class="win">勝 10( 4)k/ 3d</span>
			<br>
			<span class="lose">負  9( 1)k/ 6d</span>
			<br>
		</div>
	</body>
</html>
```

```css
body 
{
	font-family: "UD デジタル 教科書体 N-B";
	font-size: 24px;
	color: #fff;
	line-height: 26px;

	text-shadow: 
		black 2px 0px,  black -2px 0px,
		black 0px -2px, black 0px 2px,
		black 2px 2px , black -2px 2px,
		black 2px -2px, black -2px -2px,
		black 1px 2px,  black -1px 2px,
		black 1px -2px, black -1px -2px,
		black 2px 1px,  black -2px 1px,
		black 2px -1px, black -2px -1px;
}

.win
{
	color: hsl(0, 100%, 61%);
}

.lose
{
	color: aqua;
}

.note
{
	font-size: 20px;
}
```

(Web公開されるHTMLじゃないのでダサい系HTMLのまま。。)


配信で肝となるのがBattleResult.htmlのリロード処理です。

```html
<script type="text/JavaScript">setTimeout("location.href = 'BattleResult.html';", 3000);</script>
```

OBSのBrowserSourceは(少なくともローカルファイルをターゲットとした場合)自動リロードが入りません。

明示的にリロード処理挟まないといつまで経っても昔の戦績が表示されてしまうため、無理やり自己リロードさせています。


[OutputHtmlBase.html](https://github.com/kokeiro001/IksmClientDotNet/blob/e06214ed0a1f0f13112b731693e35c0bb6c14279/IksmClientDotNet.RecentBattleHtmlCreator/OutputHtmlBase.html)という基本となるHTMLがあって、そこに[RecentBattleResultHtmlEditor](https://github.com/kokeiro001/IksmClientDotNet/blob/e06214ed0a1f0f13112b731693e35c0bb6c14279/IksmClientDotNet.RecentBattleHtmlCreator/Services/RecentBattleResultHtmlEditor.cs#L30-L46)ってやつで必要なデータを足してます。



以上で、戦績を取得して配信画面に反映するところまで自動化できました。おめでとー。


## 今後について

### 記事の対象について

今回の記事はエンジニア向けの、主に実装方法に関する記事になりました。

非エンジニア向けの利用方法まとめについては別途気が向いたら書こうとおもいます。需要なさそうなんで多分書かないです。


### HTMLのリロード処理について

OBSのBrowserSourceのリロード処理を改善したい感あります。

現在、HTMLページ全体のリロードとなってしまっていますが、JavaScriptを用いて部分的な更新にすることでよりスムーズになります。

HTMLページ全体をリロードすると稀にcssの適用が遅れて？レイアウト崩れを起こすことがあります。

崩れる頻度はそれほど高くなく、仮に崩れたとしてもすぐにcss適用されて？期待通りのレイアウトになるため雑な無料放送なら問題ないのですがもにょっています。

ニコ生のコメントをOBS表示するために使ったりする[HTML5コメントジェネレーター](http://lib.kilinbox.net/comegene/index.cgi)ではJavaScriptを用いたいい感じの更新処理が行われています。この辺を参考に実装したいなーって感じです。


### 戦績取得のタイミングについて

現在、戦績は5分に一度取得しています。

非公開APIなので毎秒叩いたりするとBANくらうかもしれないとビビって長めのインターバルにしています。

または、試合終わったタイミングで手動で更新ボタンみたいなの押すと戦績取得してきます。1操作で戦績更新までの遅延を最大5分短くできます。

最大5分遅れるダサさ、1操作するダサさを軽減するため、リアルタイム画像解析を組み込み「試合終わったなー」というタイミングで自動でAPIを叩くという機能を入れたいと思っています。

すでに録画ファイルに対して画像解析して試合の頭から終わりまでのみを切り出すことはできています。

その処理をOBS経由のストリーム対応できれば行ける想定。。



## 参考

https://github.com/frozenpandaman/splatnet2statink

https://obsproject.com/forum/threads/browser-source-doesnt-use-all-the-html.60726/#post-264832