npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

nicolive.io

v0.0.2

Published

socket.io wrapper for live.nicovideo.jp

Downloads

7

Readme

NicoliveIo NPM version Build Status Coverage Status

ニコニコ生放送のコメントサーバーの寄生サーバー

インストール

$ npm install nicolive.io --save

なぜ?

ニコニコ生放送のコメントサーバーはtcpネットワークを使用して受信できますが、ブラウザには標準で搭載されているものがありません(多くは非標準で、ユーザーに実験機能をONにするなど、特殊の操作を強いるものが殆どです)。

このモジュールでは、ユーザーに代行してコメントサーバーに接続し、受信したxmlをパースして、socket.ioを介してユーザーへ通知します。webブラウザでコメントビューアを制作する人は、クロスドメイン制限を無視して、コメントサーバーへアクセスすることが可能になります。

課題

従来のコメントビューアと同じく、ブラウザに埋め込まれたuserSessionをクッキーから取得する手段が必要です。 このモジュールでは、userSessionを取得していることを前提にしており、ブラウザ側のユーティリティを一切提供しません。

デモ

Angular-Materialを使用したデモです。

API

class NicoliveIo constructor(requestListener) -> nicoliveIo

nicolive.ioをrequireすると、__socket.ioクラスを継承__した、nicoliveIoインスタンスと、NicoliveIoクラスを返します。

var nicoliveIo= require('nicolive.io');
var NicoliveIo= require('nicolive.io').NicoliveIo;

console.log(nicoliveIo.__proto__.__proto__);
// [
//  'checkRequest',
//  'serveClient',
//  'set',
//  'path',
//  'adapter',
//  'origins',
//  'attach',
//  'listen',
//  'attachServe',
//  'serve',
//  'bind',
//  'onconnection',
//  'of',
//  'close',
//  'on',
//  'to',
//  'in',
//  'use',
//  'emit',
//  'send',
//  'write',
//  'json'
// ]

console.log(nicoliveIo instanceof NicoliveIo);
// true

NicoliveIoコンストラクタの第一引数に任意のrequestListenerを渡すことで、socket.ioが補足しなかったrequestイベントを、渡したrequestListnerで受信できます。 以下は例では、Express4をrequestListnerに使用して、socket.ioサーバーと静的ファイルサーバーの両方をhttp://localhost:59798上に起動します。

$ npm install express --save

$ mkdir public
$ echo 'nicolive.io is available' > public/index.html

$ node app.js
# Listen at http://localhost:59798

app.js

// Dependencies
var express= require('express');
var NicoliveIo= require('nicolive.io').NicoliveIo;

// Setup express
var app= express();
app.use(express.static('public'));

// Setup nicolive.io
var server= new NicoliveIo(app);

// Boot
server.listen(59798,function(){
  console.log('Listen at http://localhost:59798');
});

nicoliveIo.listen(port,callback)

指定portでサーバーを起動します。callback関数を渡した場合、起動が完了したときに呼び出します。

nicoliveIo.close(callback)

サーバーをショットダウンします。callback関数を渡した場合、ショットダウンが完了したときに呼び出します。

nicoliveIo.on('connection',callback(clientSocket))

サーバーへ接続に成功したclientのEventEmitterインスタンスを、callback関数に渡します。 NicoliveIoはclientと接続が確立した時に、自動でイベントを追加します。追加するイベントは下記のとおりです。

clientSocketEvent:auth

clientからuserSessionを受け取り、getplayerstatusへアクセスします。userSessionが有効であればauthorizedイベントを、不正であればerrorイベントを送信します。

clientSocketEvent:view

clientからcannelIdを受け取り、getplayerstatusを経由してtcpでコメントに接続します。接続に成功すると、NicoliveIoはコメントサーバーから受信したxmlを解析し、解析結果でclientに送信し続けます。

接続中、送信するイベントは3種類あります:

  • threadイベント:{resultcode,last_res,ticket,...} 接続したコメントサーバーの情報、接続が成功した時、はじめに1度だけイベントを発行する。resultcodeが'0'なら成功、それ以外なら失敗。失敗コードの詳細はresultcodeで検索してください。
  • chatイベント:{'thread','vpos','date','date_usec','user_id','premium','no','text'} コメント・運営コメント・広告コメントのパース結果
  • chat_resultイベント:{{chat_attributes...},status} clientSocketEvent:commentを参照。statusが0であれば、コメントは受理されています

clientSocketEvent:comment

clientにauthorizedイベントを送信済みであれば、コメントします。 コメントの結果をchat_resultイベントでclientに送信します。

clientSocketEvent:error

サーバー側で発生した例外をこのイベントで補足し、clientにwarnイベントを送信します。

clientSocketEvent:disconnect

clientがdisconnectメソッドを実行すると、サーバーはただちにtcpを切断します。

clientSocketEvent:createNextStream

clientがプレミアム会員であれば、第一引数の配信idの情報を使用して、放送枠を作成します。

window.io

クライアントは、まず初めにsocket.ioの依存ファイルである/socket.io/socket.io.jsを読み込む必要があります。以下はportを59798でサーバーを起動した場合の例です。

<script src="http://localhost:59798/socket.io/socket.io.js"></script>
<script>
console.log(io);// function
</script>

io.connect(url) -> serverSocket

以下のような順序でサーバーへイベントを送信することで、コメントをリアルタイムに受信します。

<script src="http://localhost:59798/socket.io/socket.io.js"></script>
<script>
var userSession= 'user_session_000000_0000000000000000000000000000000000000000000000000000000000000000';

var serverSocket= io.connect();
serverSocket.on('connect',function(){
  console.log('connected');

  serverSocket.emit('auth',userSession);
});
serverSocket.on('authorized',function(debugPlayerStatus){
  console.log('authorized',debugPlayerStatus);

  serverSocket.emit('view','nsen/hotaru');
});

serverSocket.on('getplayerstatus',function(playerStatus){
  console.log('getplayerstatus',playerStatus);
});
serverSocket.on('thread',function(thread){
  console.log('thread',thread);

  serverSocket.emit('comment','てすてすてす');
});

serverSocket.on('chat',function(chat){
  console.log('chat',chat);
});

serverSocket.on('getpostkey',function(postkey){
  console.log('getpostkey',postkey);
});
serverSocket.on('chat_result',function(chat_result){
  console.log('chat_result',chat_result);
});
// connected
// authorized {port: "2806", addr: "omsg103.live.nicovideo.jp", thread: "1450455950", user_id: "143728", premium: "1"...}
// getplayerstatus {port: "2806", addr: "omsg103.live.nicovideo.jp", thread: "1450455950", user_id: "143728", premium: "1"...}
// thread {resultcode: "0", thread: "1450455950", last_res: 1863, ticket: "0x14facd80", revision: "1"...}
// chat {thread: "1450455950", vpos: "1276900", date: "1436653369", date_usec: "133900", user_id: "900000000"...}
// chat {thread: "1450455950", vpos: "1299400", date: "1436653594", date_usec: "286322", user_id: "900000000"...}
// chat {thread: "1450455950", vpos: "1299400", date: "1436653594", date_usec: "343074", user_id: "900000000"...}
// chat {thread: "1450455950", vpos: "1299400", date: "1436653594", date_usec: "398244", user_id: "900000000"...}
// chat {thread: "1450455950", vpos: "1299400", date: "1436653594", date_usec: "452674", user_id: "900000000"...}
// getpostkey .1436653726.KL6uM4PMV8cKULkBJqXjdmm1Ye0
// chat {thread: "1450455950", no: "1864", date: "1436653696", date_usec: "110007", mail: "184"...}
// chat_result {text:"てすてすてす", status: "0", thread: "1450455950", no: "1864", date: "1436653696", date_usec: "110007", mail: "184"...}
</script>

userSessionについて

userSessionは、ニコニコ生放送内で、以下をアドレスバーに実行すると取得できます。

javascript:alert(document.cookie.split(/;\s*/).reduce(function(other,cookie){if(cookie.match(/^user_session=/)){return cookie.split('=')[1];}else{return other;}},''));
// window.alert:"user_session_000000_0000000000000000000000000000000000000000000000000000000000000000"

以下は開発コンソールで取得する場合です。

document.cookie.split(/;\s*/).reduce(function(other,cookie){if(cookie.match(/^user_session=/)){return cookie.split('=')[1];}else{return other;}},'');
// user_session_000000_0000000000000000000000000000000000000000000000000000000000000000

serverSocketEvent:connect

起動したサーバーと接続に成功したとき、受信します。サーバーが再起動した時や、クライアントの回線異常により再接続した場合、2回以上イベントが発行することに注意してください。

var serverSocket= io.connect();
serverSocket.on('connect',function(){
  console.log('connected');
});
// connected

serverSocketEvent:authorized

サーバーのauthイベントへuserSessionを送信し、認証に成功したとき、受信します。

var serverSocket= io.connect();
serverSocket.on('connect',function(){
  console.log('connected');

  serverSocket.emit('auth',userSession);
});
serverSocket.on('authorized',function(debugPlayerStatus){
  console.log('authorized',debugPlayerStatus);
});
// connected
// authorized {port: "2806", addr: "omsg103.live.nicovideo.jp", thread: "1450455950", user_id: "143728", premium: "1"...}

serverSocketEvent:thread

サーバーのviewイベントへチャンネルidを送信し、スレッドに接続できたとき、受信します。

var userSession= 'user_session_000000_0000000000000000000000000000000000000000000000000000000000000000';

var serverSocket= io.connect();
serverSocket.on('connect',function(){
  console.log('connected');

  serverSocket.emit('auth',userSession);
});
serverSocket.on('authorized',function(debugPlayerStatus){
  console.log('authorized',debugPlayerStatus);

  serverSocket.emit('view','nsen/hotaru');
});
serverSocket.on('thread',function(thread){
  console.log('thread',thread);
});
serverSocket.on('chat',function(chat){
  console.log('chat',chat);
});
// connected
// authorized {port: "2806", addr: "omsg103.live.nicovideo.jp", thread: "1450455950", user_id: "143728", premium: "1"...}
// thread {resultcode: "0", thread: "1450455950", last_res: 1863, ticket: "0x14facd80", revision: "1"...}

serverSocketEvent:chat

チャンネルidのコメント1件につき1イベントを受信します。

var userSession= 'user_session_000000_0000000000000000000000000000000000000000000000000000000000000000';

var serverSocket= io.connect();
serverSocket.on('connect',function(){
  console.log('connected');

  serverSocket.emit('auth',userSession);
});
serverSocket.on('authorized',function(debugPlayerStatus){
  console.log('authorized',debugPlayerStatus);

  serverSocket.emit('view','nsen/hotaru');
});
serverSocket.on('thread',function(thread){
  console.log('thread',thread);
});
serverSocket.on('chat',function(chat){
  console.log('chat',chat);
});
// connected
// authorized {port: "2806", addr: "omsg103.live.nicovideo.jp", thread: "1450455950", user_id: "143728", premium: "1"...}
// thread {resultcode: "0", thread: "1450455950", last_res: 1863, ticket: "0x14facd80", revision: "1"...}
// chat {thread: "1450455950", vpos: "1276900", date: "1436653369", date_usec: "133900", user_id: "900000000"...}
// chat {thread: "1450455950", vpos: "1299400", date: "1436653594", date_usec: "286322", user_id: "900000000"...}
// chat {thread: "1450455950", vpos: "1299400", date: "1436653594", date_usec: "343074", user_id: "900000000"...}
// chat {thread: "1450455950", vpos: "1299400", date: "1436653594", date_usec: "398244", user_id: "900000000"...}
// chat {thread: "1450455950", vpos: "1299400", date: "1436653594", date_usec: "452674", user_id: "900000000"...}

serverSocketEvent:chat_result

サーバーのcommentイベントへ文章を送信し、結果が返ったとき、受信します。 コールバックの第一引数には、成否の情報statusを含んでいます。

//...
serverSocket.on('thread',function(thread){
  console.log('thread',thread);

  serverSocket.emit('comment','てすてすてす');
});
serverSocket.on('chat_result',function(chat_result){
  console.log('chat_result',chat_result);
});
// chat_result {text:"てすてすてす", status: "0", thread: "1450455950", no: "1864", date: "1436653696", date_usec: "110007", mail: "184"...}

serverSocketEvent:getplayerstatus

このイベントはデバッグ用です。自身のthreadイベントの直前に受信します。コールバックの第一引数には、サーバーが使用した接続に必要な最低限な情報と、getplayerstatusのパース前のxmlデータを含んでいます。

serverSocket.on('getplayerstatus',function(playerStatus){
  console.log('getplayerstatus',playerStatus);
});
// getplayerstatus {addr: "omsg103.live.nicovideo.jp"port: "2806"premium: "1"thread: "1450455950"user_id: "143728"xml: "<?xml version="1.0" encoding="utf-8"?>↵<getplayerstatus status="ok" time="1436653695"><stream><id>lv227714286</id><title>Nsen - 蛍の光チャンネル</title><description>Nsenからの去り際に自主的にお立ち寄り下さい。Nsenをご堪能頂いた後、また、ネットサーフィンを終え眠りにつく時などにオススメです。</description>...</getplayerstatus>"}

serverSocketEvent:getpostkey

このイベントはデバッグ用です。自身のchat_resultイベントの直前に受信します。コールバックの第一引数には、tcpサーバーへの書き込みに必要なpostkeyのみが渡されます。

serverSocket.on('getpostkey',function(postkey){
  console.log('getpostkey',postkey);
});
// getpostkey .1436653726.KL6uM4PMV8cKULkBJqXjdmm1Ye0

serverSocketEvent:warn

このイベントはデバッグ用です。サーバーが補足したエラーを受け取ります。

serverSocket.emit('auth','anonymous coward');
serverSocket.emit('view','nothing far');
serverSocket.emit('comment','Im invalid user');

serverSocket.on('warn',function(error){
  console.log(error);
});
// notlogin
// notfound
// nothread

serverSocket.emit('nickname',user_id,callback) -> {error,nickname}

静画APIへアクセスしてユーザー名をコールバック関数に渡します。ユーザーが存在しない場合でも、nicknameが-のユーザーとして扱うことに注意してください。

serverSocket.emit('nickname',143728,function(error,nickname){
  console.log(nickname);// 59naga
});

serverSocket.emit('nickname',9999999999,function(error,nickname){
  console.log(nickname);// -
});

serverSocket.emit('nickname','invalid',function(error,nickname){
  console.log(error);// idは数字を入力してください
  console.log(nickname);// undefined
});

serverSocket.emit('createNextStream',preventStreamId,callback) -> {error,nextSreamId}

自動次枠取得を試みます。枠内容はpreventStreamIdを参照します。 この機能は実験的です。

serverSocket.emit('createNextStream','lv248741026',function(error,nextSreamId){
  console.log(error);// 'コミュニティの作成・管理、ユーザー生放送はプレミアム会員のみご利用いただけます。'
  console.log(nextSreamId);// undefined
});

serverSocketEvent:end_of_thread

問い合わせたチャンネルidが終了した/終了している場合、このイベントを受信します。後述のcurrentイベントをサーバーに送信することで、次枠が開始していないか確認できます。

serverSocket.emit('current',callback(error,playerStatus))

viewイベントで問い合わせたコミュニティの配信中のplayerStatusを取得します。これは、end_of_threadイベントを受け取ってしばらく後、同じコミュニティで新しく放送が開始した時、検出できることを意味します。

Test

$ git clone https://github.com/59naga/nicolive.io.git
$ cd nicolive.io
$ npm install

$ export SESSION=your_user_session_value
$ npm test

License

MIT