ActionScript3.0でニコニコ動画からコメントをダウンロードする。

NNDDの機能拡張ということで、ここ1週間ほど暇を見つけては実装してきました。
ニコニコ動画から、特定の動画のコメント(xml形式)をダウンロードします。
以下のサイトを参考にしました。


以下、処理の流れです。

  • 動画に対して"見ているフリ"をする
  • "http://www.nicovideo.jp/api/getflv?v=<ビデオID>"にGETリクエストを投げる
  • GETリクエストに対するレスポンスで以下を得る
    • スレッドID
    • コメント取得用API(アドレス)
  • 取得したコメント取得用APIに対して所定のXMLをPOSTする
  • POSTに対するレスポンスとしてコメント(XML)が得られる


"http://www.nicovideo.jp/api/getflv?v=<ビデオID>"にリクエストを投げるのは、Adobe AIRでニコニコ動画にアクセスして、FLVをダウンロードしてみるでも説明した通りです。
流れが変わるのはその後です。
得られたレスポンスは、例えば

thread_id=1213477606&l=292&url=http://smile-pso61.nicovideo.jp/smile?v=3660857.64921&link=http://www.smilevideo.jp/view/3660857/573999&ms=http://msg.nicovideo.jp/41/api/&user_id=573999&is_premium=1&nickname=MineAP&time=1213521953&done=true&ng_rv=1

のようになっています。この中の、

に着目します。上はスレッドID、下がコメント取得用APIのアドレスです。


上のスレッドIDを使って、以下のようなXMLを作ります。

<thread res_from="-500" version="20061206" thread="<スレッドID>" />
  • res_form:コメントをいくつ取得するか。今回は最新500件。
  • version:FlushPlayerのバージョン?固定でいいらしい。
  • thread:スレッドID。

これを、コメント取得用APIにPOSTします。するとレスポンスとして、

<?xml version="1.0" encoding="UTF-8"?><packet><thread last_res="5753" result ...(以下省略)

のようなXML形式のデータを取得することができます。


上のXMLデータの中に、

<chat anonymity="1" date="*************" mail="184" no="4651" thread="**********" user_id="********" vpos="2382">うますぎわろたw</chat>

という形式がたくさん入ってます。これ一つのセットがコメントですね。
このうち、

  • vpos="2382"

というのが、コメントのタイミングだと思われます。(多分再生開始から2382ミリ秒という意味)
の間に入っているのがコメントですね。


後はこのコメントの山をダウンロードしたビデオの上に描画してやればいいわけです!
この部分を実装し終わってから次のリリースをしたいと思ってます。


上記の処理の部分のコードを抜き出して貼っておきます。自己責任でご利用ください。

public static const VIDEO_DOWNLOAD:int = 0;
public static const COMMENT_DOWNLOAD:int = 1; 

private var downLoader:URLLoader;
private var videoLoader:URLLoader;
private var commentLoader:URLLoader;


/**
 * FLVのURLを取得する為のAPIへのアクセスを行う
 * @param videoID 英数字2文字+数字 で表されるビデオのID
 * @param type ダウンロード対象がVideoか、Commentか。
 * @return APIへのリクエストを行うURLLoader
 * 
 */
private function getAPIResult(videoID:String, type:int = VIDEO_DOWNLOAD):URLLoader
{
	var loader:URLLoader = new URLLoader();
	
	//FLVのURLを取得する為にニコニコ動画のAPIにアクセスする
	var getAPIResult:URLRequest;
	getAPIResult = new URLRequest("http://www.nicovideo.jp/api/getflv?v=" + videoID);
	getAPIResult.method = "GET";
	
	if(type == VIDEO_DOWNLOAD){
		loader.addEventListener(Event.COMPLETE, accessAPISuccessForVideo);
	}else if(type == COMMENT_DOWNLOAD){
		loader.addEventListener(Event.COMPLETE, accessAPISuccessForComment);
	}
	loader.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
	loader.addEventListener(ProgressEvent.PROGRESS, progressHandler);
	loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);   
	
	loader.load(getAPIResult);

	//注意!この戻り値をdownLoaderに入れておくこと!
	return loader;
}

/**
 * APIへのアクセスに成功したら呼ばれる。
 * @param evt
 * 
 */
private function accessAPISuccessForComment(evt:Event):void
{
	this.label_status.text = "アドレス取得に成功";
	trace("アドレスの取得に成功" + evt);
	
	commentLoader = this.getComment(downLoader);
	
}

/**
 * コメントを取得するところ。
 * @param getApiResultLoader
 * @return 
 * 
 */
private function getComment(getApiResultLoader:URLLoader):URLLoader
{
	//<thread res_from="-500" version="20061206" thread="スレッドID" />
	
	//thread_id=1213477606&l=292&url=http://smile-pso61.nicovideo.jp/smile?v=3660857.64921&link=http://www.smilevideo.jp/view/3660857/573999&ms=http://msg.nicovideo.jp/41/api/&user_id=573999&is_premium=1&nickname=MineAP&time=1213521953&done=true&ng_rv=1
	trace(unescape(getApiResultLoader.data));
	
	
	//APIから得られたデータの"thread_ID="にあるスレッドIDを探す
	var threadId:String = getApiResultLoader.data.substring(getApiResultLoader.data.indexOf("thread_ID=")+11, getApiResultLoader.data.indexOf("&"));
	//APIから得られたデータの"&ms="にあるURLを探す
	var commentURL:String = getApiResultLoader.data.substring(getApiResultLoader.data.indexOf("&ms=")+4, getApiResultLoader.data.indexOf("&", getApiResultLoader.data.indexOf("&ms")+1));
	
	var getComment:URLRequest = new URLRequest(unescape(commentURL));
	getComment.method = "POST";
	getComment.requestHeaders = new Array(new URLRequestHeader("Content-Type", "text/html"));
	
	videoType = ".xml";
	
	//var xml:String = "<thread fork=\"1\" user_id=\"" + user_id + "\" res_from=\"1000\" version=\"20061206\" thread=\"" + threadId + "\" />";
	var xml:String = "<thread res_from=\"-500\" version=\"20061206\" thread=\"" + threadId + "\" />"; 
	getComment.data = xml;
	
	var loader:URLLoader = new URLLoader();
	loader.dataFormat=URLLoaderDataFormat.TEXT;
	loader.addEventListener(Event.COMPLETE, commentLoadSuccess);
	loader.addEventListener(ProgressEvent.PROGRESS, progressHandler);
	loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
	loader.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
	
	loader.load(getComment);
	
	//http://msg.nicovideo.jp/41/api/,<thread res_from="-500" version="20061206" thread="1213477606" />
	trace(getComment.url + "," + unescape(new String(getComment.data)));	

	trace("xml取得開始");
	
	return loader;
	
}

/**
 * xmlのダウンロードが完了したら呼ばれる
 * @param evt
 * 
 */
private function commentLoadSuccess(evt:Event):void
{
	this.label_status.text = "XMLダウンロード成功"
	trace("XMLダウンロード成功" + evt);
	
	//trace(commentLoader.data)
	//<?xml version="1.0" encoding="UTF-8"?><packet><thread last_res="5753" result ...(以下省略)
	
	
	if(this.saveComment(commentLoader)){
		this.label_status.text = "XML保存完了";
	}else{
		this.label_status.text = "XML保存失敗";
	}
	
	this.qOrE();
}