Nodejsでニコ生のコメビュを作る

```js:d.hatena.ne.jp/toriimiyukki/20120124/1327417154
var net = require('net');
socket.connect(data.msPort, data.msAddr);
socket.setEncoding('utf-8');
socket.on('connect', function() {
    console.log('connected');
    socket.write('<thread thread="' + data.msThread + '" res_from="' + data.resFrom + '" version="20061206" >' + "\0");
});
socket.on('data', function(data) {
    console.log(data);
});
```
[グーグルで検索すると見つかるこのコード片では](http://d.hatena.ne.jp/toriimiyukki/20120124/1327417154)、__getplayerstatusを取得しないので__動きません。

> * [手動でAPIにアクセスして](http://live.nicovideo.jp/api/getplayerstatus/nsen/hotaru)、該当箇所を書き換えると動きます。
  * `data.msPort`: &lt;getplayerstatus&gt;&lt;ms&gt;&lt;port&gt;
の値
  * `data.msAddr`: &lt;getplayerstatus&gt;&lt;ms&gt;&lt;addr&gt;
の値
  * `data.msThread`: &lt;getplayerstatus&gt;&lt;ms&gt;&lt;thread&gt;
の値
  * `data.resFrom`: `-1000~0` コメント開始位置

## 例

```js:suck.js
var net= require('net');
var socket= net.connect(2811,'omsg101.live.nicovideo.jp');
socket.on('connect', function(){
  console.log('connected');
  socket.setEncoding('utf-8');
  socket.write('<thread thread="1435806943" res_from="-5" version="20061206" />\0');
});
socket.on('data',function(data){
  console.log(data);
});
```
```xml:data
<thread resultcode="0" thread="1435806943" last_res="2836" ticket="0x1dfc9b00" revision="1" server_time="1431262237"/><chat thread="1435806943" vpos="6450700" date="1431261909" date_usec="57301" mail="184" user_id="Idj1TdXOGrERXdFOUdiTX4sKIB0" anonymity="1" locale="jp">v(ﾟДﾟ)v＜♪ ｶｧｰﾆｰ ｶｧｰﾆｰ ｼｮﾇﾀﾞｰ ｶｼﾞｶｼﾞ ﾊﾞｰﾌﾞｰ ｿﾑﾀﾞｰ ﾊﾟｯ! ♬</chat><chat>...</chat>
```

__This sucks.__
この記事では、ログインから順を追って説明します。

## ログイン
`https://secure.nicovideo.jp/secure/login`に`mail_tel`と`password`を`POST`します。
[request](https://www.npmjs.com/package/request)モジュールを使用します。

```bash
$ npm install request
$ node login.js
```
```js:login.js

var request= require('request');

request.post({
  url: 'https://secure.nicovideo.jp/secure/login',
  form: {
    mail_tel: 'your@mail.address',
    password: '********',
  },
},function(error,response){
  if(error!=null) throw error;

  var session= null;
  var cookies= response.headers['set-cookie'] || [];
  for(var i=0; i<cookies.length; i++){
    var cookie= cookies[i];
    if(cookie.match(/^user_session=user_session/)){
      session= cookie.slice(0,cookie.indexOf(';')+1);
    }
  }
  
  console.log(response.headers['set-cookie']);
  console.log(session);
});
```

ログインが正常であれば

```js:response.headers['set-cookie']

[ 'nicosid=1431257919.1222609129; expires=Wed, 07-May-2025 11:38:39 GMT; path=/; domain=.nicovideo.jp',
  'user_session=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT',
  'user_session=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/',
  'user_session=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/; domain=.nicovideo.jp',
  'user_session=user_session_000000_0000000000000000000000000000000000000000000000000000000000000000; expires=Tue, 09-Jun-2015 11:38:39 GMT; path=/; domain=.nicovideo.jp',
  'repair_history=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/; domain=.nicovideo.jp' ]
```
```js:session
user_session=user_session_000000_0000000000000000000000000000000000000000000000000000000000000000;
```
のような値が返ります。最終的に使用するのは`session`のみです。

> 以降はこの`session`を使いまわします。`session`はしばらく有効で、ローカルファイルとして値を保存しておくことで、メソッド毎にログイン処理を挟まずに済むでしょう。

## プレイヤー（コメントサーバー）情報の取得

では早速、getplayerstatusが使用可能になったことを確認しましょう。

```js:getplayerstatus.js
var request= require('request');
request({
  url: 'http://live.nicovideo.jp/api/getplayerstatus/nsen/hotaru',
  headers: {
    Cookie: session,
  },
},function(error,response){
  if(error!=null) throw error;

  console.log(response.body);
});
```
```xml:response.body
<?xml version="1.0" encoding="utf-8"?>
<getplayerstatus status="ok" time="1431262800"><stream><id>lv220580424</id><title>Nsen - 蛍の光チャンネル</title><description>Nsenからの去り際に自主的にお立ち寄り下さい。Nsenをご堪能頂いた後、また、ネットサーフィンを終え眠りにつく時などにオススメです。</description>...</stream>
...
<ms><addr>omsg101.live.nicovideo.jp</addr><port>2811</port><thread>1435806943</thread></ms>
...
</getplayerstatus>
```

プレイヤー（コメントサーバー）情報を取得することができました。
> getplayerstatusは [nsen](http://live.nicovideo.jp/api/getplayerstatus/nsen/vocaloid),  [lv***](http://live.nicovideo.jp/api/getplayerstatus/lv220346524),  [co***](http://live.nicovideo.jp/api/getplayerstatus/co2183236)の３種類が使用可能です。

[cheerio](https://github.com/cheeriojs/cheerio)モジュールを使用して、必要な値だけを取り出します。

```bash
$ npm install cheerio
$ node getplayerstatus.js
```
```js:getplayerstatus.js
var request= require('request');
var cheerio= require('cheerio');

request({
  url: 'http://live.nicovideo.jp/api/getplayerstatus/nsen/hotaru',
  headers: {
    Cookie: session,
  },
},function(error,response){
  if(error!=null) throw error;

  var $= cheerio.load(response.body);
  console.log($('getplayerstatus ms port').text());
  console.log($('getplayerstatus ms addr').text());
  console.log($('getplayerstatus ms thread').text());
});
```
```js:console.log
2811
omsg101.live.nicovideo.jp
1435806943
```

## プレイヤーへ接続する
これまでのコードを繋ぎあわせて、コメントを取得します。

```js:nicolive.js
// Dependencies
var request= require('request');
var cheerio= require('cheerio');
var net= require('net');

// Demonstration
process.nextTick(function(){
  // Environment
  var id= 'your@mail.address';
  var pw= '********';
  var live_id= 'nsen/hotaru';

  // Boot
  nicolive.login(id,pw,function(error,session){
    if(error!=null) throw error;

    nicolive.fetchThread(live_id,session,function(error,thread){
      if(error!=null) throw error;

      nicolive.view(thread,function(error,viewer){
        if(error!=null) throw error;

        viewer.on('data',function(data){
          console.log(data);
        });
      });
    });
  });
});

// Methods
nicolive= {
  login: function(id,pw,callback){
    request.post({
      url: 'https://secure.nicovideo.jp/secure/login',
      form: {
        mail_tel: id,
        password: pw,
      },
    },function(error,response){
      if(error!=null) return callback(error);

      var session= null;
      var cookies= response.headers['set-cookie'] || [];
      for(var i=0; i<cookies.length; i++){
        var cookie= cookies[i];
        if(cookie.match(/^user_session=user_session/)){
          session= cookie.slice(0,cookie.indexOf(';')+1);
        }
      }
      if(session==null) return callback(new Error('Invalid user'));

      callback(null,session);
    });
  },
  fetchThread: function(live_id,session,callback){
    request({
      url: 'http://live.nicovideo.jp/api/getplayerstatus/'+live_id,
      headers: {
        Cookie: session,
      },
    },function(error,response){
      if(error!=null) return callback(error);

      var $= cheerio.load(response.body);
      callback(null,{
        port: $('getplayerstatus ms port').text(),
        addr: $('getplayerstatus ms addr').text(),
        thread: $('getplayerstatus ms thread').text(),
      });
    });
  },
  view: function(thread,callback){
    var viewer= net.connect(thread.port,thread.addr);
    viewer.on('connect', function(){
      viewer.setEncoding('utf-8');
      viewer.write('<thread thread="'+thread.thread+'" res_from="-5" version="20061206" />\0');

      callback(null,viewer);
    });
  },
}

module.exports= nicolive;
```

```bash
$ node nicolive.js
```
```xml:console.log(data);
<thread resultcode="0" thread="1435806943" last_res="2836" ticket="0x1dfc9b00" revision="1" server_time="1431262237"/><chat thread="1435806943" vpos="6450700" date="1431261909" date_usec="57301" mail="184" user_id="Idj1TdXOGrERXdFOUdiTX4sKIB0" anonymity="1" locale="jp">v(ﾟДﾟ)v＜♪ ｶｧｰﾆｰ ｶｧｰﾆｰ ｼｮﾇﾀﾞｰ ｶｼﾞｶｼﾞ ﾊﾞｰﾌﾞｰ ｿﾑﾀﾞｰ ﾊﾟｯ! ♬</chat><chat>...</chat>
```

## 成果物（Electron製コメントビューアー）
![d06cf958-ebda-11e4-9d22-5f9420129523.png](https://qiita-image-store.s3.amazonaws.com/0/28576/59adc15b-9d61-439b-7392-62b98daaddfa.png)

* [edgy-comment-viewer/59naga - Latest release | github](https://github.com/59naga/edgy-comment-viewer/releases/latest)
* [nicolive/59naga Command line comment viewer | github](https://github.com/59naga/nicolive)
* [nicolive.io/59naga ニコニコ生放送のコメントサーバーの寄生サーバー | github](https://github.com/59naga/nicolive.io)
* [nicolive.berabou.me/59naga HTML5コメントビューアー | github](https://github.com/59naga/nicolive.berabou.me)

## 参考
* [request | npm](https://www.npmjs.com/package/request)
* [cheerio | npm](https://www.npmjs.com/package/cheerio)
* [niconicoのメッセージ(コメント)サーバーのタグや送り方の説明 - 神の味噌汁青海](https://web.archive.org/web/20141224124355/http://blog.goo.ne.jp/hocomodashi/e/3ef374ad09e79ed5c50f3584b3712d61)
* [ニコニコAPIリストwiki](http://www59.atwiki.jp/nicoapi/)
* [node-nicovideo-api/Ragg- | github](https://github.com/Ragg-/node-nicovideo-api)