ニコニコ動画
ニコニコ生放送

ニコ生のコメント送受信をWebSocket+JSONでやる方法ざっくり解説

ニコニコ生放送のHTML5版ページでブラウザコンソールからNetworkを覗いてみるとこんなのが見つかります。

image.png

JSONでコメントのやり取りしてますね。

「あれ? ニコ生コメントってXMLでやり取りをしていたんじゃ?」

と記憶されている方もいるかと思います。

古い方のFlash版プレイヤーではXMLWebSocketでやり取りしていたようですが、新しい方のHTML5プレイヤーではWebSocket+Jsonでコメント送受信しているようです。

そのへんについてこの記事でざっくり解説していきましょう。

コメント送受信の全体像

ニコ生の新配信では「視聴セッション」と「コメントセッション」となる2つのWebSocketによるやり取りが必要になります。(公式にそう呼ばれているわけではなく、説明の便宜上「○○セッション」と呼び分けてます。)

ニコ生新配信の視聴セッションについての解説はこちら

視聴セッションの方で説明されている生放送情報の「leoPlayerProps」がコメントセッションでも必要になります

その上で、コメント送受信の全体の流れとしては…

  • 視聴セッション接続後に受信できる currentRoom コマンドからコメントセッションの WebSocket URL を取得
  • コメントセッションWebSocket URLを使って接続を開始(WebSocketリクエストヘッダーに 「Sec-WebSocket-Protocol: msg.nicovideo.jp#json」が必要
  • thread情報(過去コメ、コメ数、投稿チケットなど)を送出してもらうための thread メッセージを送信(最初の画像の一番上の緑のラインのやつ)
  • thread メッセージ(1個)と res_from で指定した数を上限としたchat メッセージ(n個)
  • chatメッセージの逐次受信
  • コメント送信と結果受信

以上の流れになります。以下ではこれらの要点をかい摘んで説明していきます。

コメントセッションのWebSocket URLを取得

コメントセッションを行うには、HTML5プレイヤー生放送ページに埋め込まれているleoPlayerPropsから視聴セッションをWebSocketで接続して、getpermit コマンドを送信することで受信できる currentRoom コマンドの情報が必要です。

視聴セッションの接続については以下のリンクをどうぞ

ニコ生新配信の視聴セッションについて

視聴セッションでgetpermitを送信した後に、以下のような currentroom コマンドが送られてきます

{
    "type": "watch",
    "body": {
        "room": {
            "messageServerUri": "ws://omsg105.live.nicovideo.jp:81/websocket",  //WebSocketURL
            "messageServerType": "niwavided", 
            "roomName": "アリーナ",
            "threadId": "1623260004",
            "forks": [0],
            "importedForks": []
        },
        "command": "currentroom"
    }
}

messegeServerUriで指定されているURLがここでいうWebSocket URLです。

コメントセッションに接続する

先ほど出てきたmessageServerUriに対してWebSocketで接続していきますが、そのままだとサーバーから接続がリセットされてしまうかと思います。

これはWebSocketにニコニコ生放送のコメントをやり取りできることを示す以下のようなリクエストヘッダの設定が必要なようです

key "Sec-WebSocket-Protocol"
value "msg.nicovideo.jp#json"

コメントセッションのメッセージ

  • ping(受信)
  • thread(送信・受信)
  • chat(送信・受信)
  • chat_result(受信)

の4種類について説明していきます

ping メッセージの受信

コメントセッションでは、メッセージ送信の後に以下のようなシーケンス制御?の ping メッセージが到着し、続けてコメント情報やコメント結果を受け取ることになります。(そして「閉じ」のpingメッセージがきて要求送信+結果受信のシーケンスが完了するようです)

{"ping":{"content":"rs:0"}}
{"ping":{"content":"ps:0"}}
{"thread":{...}} 
{"chat":{...}} 
{"chat":{...}} 
{"chat":{...}} 

(以下chatがたくさん)

{"ping":{"content":"pf:0"}}
{ ping":{"content":"rf:0"}}

詳細な仕様はわかりませんが、この ping メッセージはおそらく多重送受信を防止するためのシーケンス管理のためにあるのかと思います。

thread メッセージの送信

コメントセッション接続後に以下のメッセージを送信することで、生放送の過去コメントや投稿チケットなどを送り返して貰えます。

[{"ping":{"content":"rs:0"}},{"ping":{"content":"ps:0"}},{"thread":{"thread":"{ThreadId}","version":"20061206","fork":0,"user_id":"{UserId}","res_from":-1000,"with_global":1,"scores":1,"nicoru":0}},{"ping":{"content":"pf:0"}},{"ping":{"content":"rf:0"}}]
[{
    "ping": {
        "content": "rs:0"
    }
}, {
    "ping": {
        "content": "ps:0"
    }
}, {
    "thread": {
        "thread": "{ThreadId}",
        "version": "20061206",
        "fork": 0,
        "user_id": "{UserId}",
        "res_from": -1000,
        "with_global": 1,
        "scores": 1,
        "nicoru": 0
    }
}, {
    "ping": {
        "content": "pf:0"
    }
}, {
    "ping": {
        "content": "rf:0"
    }
}]

thread メッセージの受信

{"thread":{"resultcode":0,"thread":{ThreadId},"last_res":19402,"ticket":"{Ticket}","revision":1,"server_time":1522396433}}

resultcode 接続結果、0 成功 0以外で失敗
last_res コメント数
ticket コメント投稿時に必要なチケット
revision リビジョン? NG設定が反映された回数?
servertime 接続時のサーバー時間 UnixTime

chat メッセージの受信

{"chat":{"thread":{ThreadId},"vpos":2812853,"date":1522395534,"date_usec":911780,"mail":"184","user_id":"{UserId}","anonymity":1,"locale":"ja-jp","content":"{ChatText}"}}

thread ThreadId
vpos 放送部屋のオープン時間を元にしたコメント位置(10ミリ秒単位)
mail コメントのコマンド(「184」「shita」など)
user_id ユーザーID (数字のみの文字列、184による匿名コメントの場合は英数字混じりの文字列)
content コメント本文
date 投稿時間 UnixTime
date_usec 投稿時間のマイクロ秒

(以下オプション、無い場合もあります)
anonimity 匿名フラグ 1なら匿名
premium 1:プレミアム会員 2:運営
score 0またはマイナスの数字
origin ?

vpos について補足

vposの基準時間は放送の「開始時間」(beginTime)ではなく「オープン時間」(openTime)です。ニコ生は放送開始前でも座席についてコメント可能になるという仕様なので、アプリ側で厳密にコメント表示位置を決めたい場合は「オープン時間」を参照しましょう。

chat メッセージの送信(放送へのコメント投稿)

{"chat":{"thread":"{info.ThreadId}","vpos":{vpos},"mail":"{command}","ticket":"{ticket}","user_id":"{info.UserId}","content":"{content}","postkey":"{postKey}"}}

thread ThreadId
vpos 放送部屋のオープン時間を元にしたコメント位置(10ミリ秒単位)
mail コメントのコマンド(「184」「shita」など)
ticket threadメッセージ受信時のticketを指定する
user_id コメント投稿するユーザーID(放送ページアクセスのログインセッションと同一のユーザーID)
content コメント本文
postkey (後述)

PostKey は視聴セッションから取得する

コメント投稿に必要になる PostKey は視聴セッションを通じて取得する必要があります。
PostKeyを取得するには、視聴セッションのWebSocketに以下の getpostkey コマンドを送信します

{"type":"watch","body":{"command":"getpostkey","params":["{threadId}"]}}

getpostkey コマンドを送信すると、視聴セッションに以下のような postkey コマンドが受信できます

{"type":"watch","body":{"command":"postkey","params":["{postkey}",null,"{ThreadId}"]}}

コメントセッションでコメントを投稿すると、以下のような流れでメッセージを受信できます

  • ping *2
  • chat_result
  • chat (成功していた場合)
  • ping *2

コメント投稿が成功した場合は、chat メッセージが受信できます。アプリ側の表示としてはchatメッセージ受信を元に表示追加を行うだけで対応が足りるかと思います。

投稿に失敗していた場合は次のchat_resultで説明してます↓↓↓

chat_result メッセージの受信

{"chat_result":{"thread":{ThreadId},"status":0,"no":{comment_number}}}

status が 0 の場合はコメント成功で、0 以外の場合は失敗です。

status が 4 の場合は invalid_postkey のため PostKey を再取得してコメントを送信し直す必要があります。

運営コメントについて

chat メッセージ受信の際、premium が 2 の場合は運営コマンドです。

chatメッセージの content に「放送の延長」「ランキング情報」「ニコニ広告の情報」などのデータが渡されます。

/info 3 90分延長しました

といったコメント文なので、アプリ側の表示として流れるコメントでは表示しないようにするなど、一般のコメントとは区別して表示できるとベターかと思います。

詳しくは ニコニコ大百科ページ - ニコニコ生放送 運営コマンドまとめ をどうぞ

(WebSocket+Jsonでの/vote の挙動とか調べたいんですけど手が回ってないので誰かやってください❤)

実装

C#と.Netの環境での実装↓
NiwavidedNicoLiveCommentClient
WebSocketとNewtonsoft.Jsonがほとんど足回りをカバーしているのでシンプルにアプリ部分だけ処理しています

ただchatメッセージやthreadメッセージなどの型は別のライブラリを参照しているので、読み解く場合やや面倒
https://github.com/tor4kichi/OpenNiconico2/tree/master/Mntone.Nico2/Mntone.Nico2.Shared/Videos/Comment

本来ならニコニコサービス関連のコードはここで登場したOpenNiconico2といったライブラリ側に実装をやるべきなんですよね。なんとかしなくちゃ(使命感)

参考リンク

3DS版「ニコニコ」で使用しているAPIまとめ -
ニコニコの API V1 のアクセス制限はじまる - ニコラボ

「ニコニコの API V1 のアクセス制限はじまる」のリンクは関係なさそうに見えるかもですが、生放送IDからJSONの放送情報が取れるAPIが載っていて助かりました