//========================================================================================================
// 棒読みちゃんで○つのが悲鳴を上げる事案が発声
// 略称 ぼつのひめ
// 
// プログラム概要
// 式姫の庭のチャットを棒読みちゃんに読み上げさせるアプリです。
// 
// 実行環境
// IE専用 IE6以上
// BASP21のインストールが必要
// 棒読みちゃん Ver0.1.8.3以降
// 式姫の庭をフレームなしでの起動が必要
// 
// 作成者 MSX.X68
// リポジトリ http://sourceforge.jp/users/msx_x68/
//
//========================================================================================================
// 
// HISTORY
// 2014.7.27  version 0.10 暫定公開
// 2014.7.29  v0.11
//            ちらつき対策 write前にBODYの背景属性に最初から画像を指定
//            動作速度改善 チャット取得はIEのBUSY状態解除を待つように
// 2014.7.31  チャットの流れる方向を指定可能に
// 2014.8.8   v0.20
//            設定ファイルから設定を読み込むように
//            チャットの内容読込みスクリプトをselector.jsから読み込むように
//            重複判定(簡易版)を追加。重複排除ではなく、読み上げ済みの判定を間違う場合の暫定対処
// 2014.8.22  v0.21
//            読み上げ対象サイトがない場合と見失った場合は待機して一定間隔で探しに行くように
//            チャット１発言をHTMLタグの他にクラス名でも指定できるように
// 2014.8.27  重複判定(正式対応)を追加。
// 2014.9.6   棒読みちゃん不在の場合でも終了しないように
//            棒読みちゃんへの通信エラーの場合でも終了しないように
//            読み上げ対象サイトがない場合はウグイスに「待機中･･･」を表示
//            動作中のポップアップ表示のON/OFFを設定に追加
// 2014.9.7   v0.30
//            selector.jsの例外内容を表示するように
//            サイト別設定でURLを複数指定可能に
//            ウグイスのウィンドウサイズを固定に
//            v0.20→v0.21の変更点が多いのでv0.30に
// 2014.9.13  ウグイスの画像更新処理を変更
// 
//========================================================================================================

var VERSION = "0.30";

//----------------------------
// 設定
//----------------------------

//設定ファイルのフォルダ名、ファイル名
var ht_SETTING_PATH = {
	"SETTINGS_DIR"				:"settings",		//設定フォルダ
	"BASE_SETTING_FILE"			:"base_setting.txt",//基本設定ファイル
	"SITE_SETTING_DIR_PREFIX"	:"setting_",		//サイト別設定フォルダの接頭語
	"SITE_SETTING_FILE"			:"setting.txt",		//サイト別設定ファイル
	"SELECTOR"					:"selector.js"		//チャットセレクタ
};

//設定
var ht_setting = {
	"SETTING_NAME":"",	//設定名
	"URL":null,			//チャットを取得する対象のURL(に含まれるべき文字列)
	"DIRECTION":"",		//チャットの流れる方向。PUSH_BOTTOM=下から押す PUSH_TOP=上から押す
	"CHAT_ID":"",		//チャットの一覧が含まれるHTML要素のID
	"CHAT_ITEM_TAG":"",	//チャットの1個の発言が含まれるHTMLタグ/クラス名
	"CHAT_ITEM_TYPE":"",//CHAT_ITEM_TAG がHTMLタグならTAG クラス名ならCLASS
	"UNIQUE":"",		//重複判別方法 ALL=名前+時刻+内容 NAMECONTENT=名前と内容 CONTENT=内容のみ NONE=チェックしない
	"UGUIS":"",		//ウグイス指定。uguis\xxxxx のxxxxx
	"FRAME_PARENT":"",	//取得対象がフレームの場合のフレームの親のURL(に含まれるべき文字列)。警告表示用。
	"POPUP":"",			//エラー検出時のポップアップ表示 0:表示しない 10:表示する

	"POLLING_INTERVAL":0,	//チャット取得間隔(単位 ミリ秒)
	"TALK_INTERVAL":0,		//読み上げ中の棒読みちゃんの状態取得間隔(単位 ミリ秒)
	"IE_BUSY_INTERVAL":0,	//IEの状態取得間隔(単位 ミリ秒)
	"CHAT_MAX_LINE":0,		//何行取得するか？
	"SELECTOR":""			//チャットセレクタのスクリプト
};

//----------------------------
// 棒読みちゃん操作用定数
//----------------------------
var sHostName = "localhost";	//棒読みちゃんが実行されているマシン
var iPort = 50001;				//棒読みちゃんの接続ポート
var lengthData = {
	"byte"	:1,
	"short"	:2,
	"int"	:4
	};

//----------------------------
// 棒読みちゃん操作用変数
//----------------------------
var	iCommand = 0x0001;	//	short コマンド(0x0001)
var	iSpeed   = -1;		//	short 速度(-1：デフォルト, 50〜300)
var	iTone    = -1;		//	short 音程(-1：デフォルト, 50〜200)
var	iVolume  = -1;		//	short 音量(-1：デフォルト,  0〜100)
var	iVoice   = 0;	//	short 声質( 0：デフォルト,  1〜8:AquesTalk, 2001〜:AquesTalk2, 10001〜:SAPI5)
						//	1:女性1、2:女性2、3:男性1、4:男性2、5:中性、6:ロボット、7:機械1、8:機械2
						//	2001〜:AquesTalk2、10001〜:SAPI5）
var	bCode    = 0;		//	byte  文字列の文字コード(0:UTF-8, 1:Unicode, 2:Shift-JIS)



//----------------------------
// ウグイス操作用変数
//----------------------------
var settingUguis = {
	"isUguis":true,					//ウグイス連動を行う場合 true
	"pathUguis":"uguis",			//ウグイスフォルダ
	"strUguisName":"sample1",		//ウグイスの指定
	"objUguisIE":null,				//ウグイスを表示しているIEオブジェクト
	"silent":null,					//何もしていないときの画像
	"speak":null,					//発言中の画像
	"guild":null,					//同盟通知
	"system":null,					//システム通知
	"keyword":null,					//キーワードリスト
	"max_keyword":50				//キーワード最大設定件数
}

while(1) {

//----------------------------
// 変数
//----------------------------
var OBJ_FSO = WScript.CreateObject("Scripting.FileSystemObject"); //ファイルストリーム
var OBJ_SHELLAPP = WScript.CreateObject("Shell.Application").Windows(); //シェルプログラム一覧

//----------------------------
// メイン処理
//----------------------------

var objGameIE = null; //ゲーム画面を表示しているIEオブジェクト
var objChat = null; //ゲーム画面中のチャット一覧を表示しているHTML要素
var strDirection = "PUSH_BOTTOM"; //チャットの流れる方向。PUSH_BOTTOM=下から押す PUSH_TOP=上から押す


//基本設定読込
clear_setting();
read_base_setting();

// 起動中シェルをすべて取得
for( var i=0 ; i < OBJ_SHELLAPP.Count ; i++ ) {

	var OBJ = OBJ_SHELLAPP.item(i);
	if(OBJ == null) {
		// シェル一覧の途中が抜けている場合はスキップ
		continue;
	}
	// 実行ファイル名からIEを探す
	STR_ProgramName = OBJ_FSO.GetFileName(OBJ.FullName);
	if( STR_ProgramName.toLowerCase() == "iexplore.exe" ) {
		// IEが見つかったら式姫の庭のURLを探す
		var setting_list = new Array(); //読込済のサイト別設定の接尾語リスト
		var isDetect = false;

		//サイト別設定読込
		while(scan_site_setting(setting_list) != null) {
			for(var j=0;j<ht_setting["URL"].length;j++) {
				if( OBJ.LocationURL.indexOf( ht_setting["URL"][j] ) >= 0) {
					isDetect = true;
					alert_popup(ht_setting["SETTING_NAME"]+" の\n"+(j+1)+" 番目のURLを使用します");
					//ウグイス設定を読み込む
					setUguis(settingUguis);
					break;
				}
			}
			if(isDetect) {
				break;
			}
		}

		if(isDetect==true) {
			var element_chat = null; //チャット全体を表示しているHTML要素

			// 式姫の庭のURLが見つかったらチャットを表示しているHTML要素を探す
			element_chat = OBJ.document.getElementById(ht_setting["CHAT_ID"]);
			if(element_chat != null) {
				// 目的のHTML要素が見つかった
				objGameIE = OBJ;
				objChat = element_chat;
				strDirection = ht_setting["DIRECTION"];
				break;
			} else {
				// 目的のHTML要素が見つからない
				alert_popup( OBJ.LocationURL+" のHTMLの中に\nID="+ht_setting["CHAT_ID"]+" のHTML要素が見つかりませんでした。" );
			}
		}
	}
}

//----------------------------
// ウグイス用IE表示
//----------------------------
if(settingUguis["isUguis"]) {
	if(settingUguis["objUguisIE"] == null) {
		//ウグイス用IEが起動されていなければ起動する
		settingUguis["objUguisIE"] = bootUguisIE(settingUguis);
	} else {
		//WScript.echo("ウグイス起動済み");
	}
}

//----------------------------
// チャット取得処理開始
//----------------------------
if( (objGameIE != null) && (objChat != null)) {
	//サイトが存在かつチャットのHTML要素が存在すればチャット取得処理開始
	loop_get_chat(objChat,objGameIE,settingUguis);
	//loop_get_chat を抜けてくるのは例外が発生した場合。
	//サイトを移動したと判断する。
	//WScript.echo("サイト見失った！！！");
}

//----------------------------
// 読み上げ対象サイトが見つからなかった場合
//----------------------------
if(settingUguis["isUguis"]) {
	var str="待機中･･･";
	//画像書き換え
	bg_change(settingUguis,str,settingUguis["silent"]);
}
WScript.sleep(15000); //15秒待つ

}

WScript.Quit(1);



//================================================================
//================================================================
//
// チャット操作関数
//
//================================================================
//================================================================

//================================================================
//	チャット取得ループ
//================================================================
function loop_get_chat(element_chat,obj_ie,settingUguis) {
	
	var newest_chat = new Array(ht_setting["CHAT_MAX_LINE"]); //最新のチャットリスト
	var old_chat = new Array(ht_setting["CHAT_MAX_LINE"]); //前回のチャットリスト
	var start = 0; //newest_chatの何番目からが新しいチャットか
	var isSilent = true; //更新がなかった場合true。ウグイスのちらつき防止用
	var i=0;

	var iPassTime = 0; //ポーリング後の経過時間
	var interval = 0; //次のポーリングまでのスリープ時間

	//起動直後は更新なしにするため、ループ開始前に取得しておく
	newest_chat = get_newest_chat( element_chat , ht_setting["CHAT_MAX_LINE"] );

	while(1) {
		//IE busyの間待機する。動作速度改善策
		if(obj_ie != null) {
			try {
				waitIE(obj_ie);
			} catch(e) {
				var err_str = "IE状態確認中に 例外が発生しました。\n"+
								"error number = "+(e.number & 0xFFFF)+"\n"+
								"error name ="+e.name+"\n"+
								"error message ="+e.message;
				alert_popup(err_str);
				BouyomiTalk("ブラウザ状態確認中に 例外が発生しました。"+ht_setting["SETTING_NAME"]+"を見失いました。",
						iCommand,iSpeed,iTone,iVolume,iVoice,bCode);
				return;
			}
		}

		// 最新の内容を前回の内容にコピー
		old_chat = newest_chat.concat();
		try {
			//最新チャットを取得
			newest_chat = get_newest_chat( element_chat , ht_setting["CHAT_MAX_LINE"] );
		} catch(e) {
			var err_str = "チャット取得中に 例外が発生しました。\n"+
							"error number = "+(e.number & 0xFFFF)+"\n"+
							"error name ="+e.name+"\n"+
							"error message ="+e.message+"\n";
			alert_popup(err_str);
			BouyomiTalk("チャット取得中に 例外が発生しました。"+ht_setting["SETTING_NAME"]+"を見失いました。",
						iCommand,iSpeed,iTone,iVolume,iVoice,bCode);
			return;
		}

		//newest_chatの何番目から表示するかを得る
		start = diff_chat( newest_chat , old_chat );

		iPassTime = 0;
		if( start >= 0 ) {
			//新しい発言がある
			isSilent = false;
			for(i=start;i<newest_chat.length;i++) {
				//WScript.Echo("["+newest_chat[i]["time"]+"]["+newest_chat[i]["name"]+"] "+newest_chat[i]["content"]); //ここを有効にすると(cscriptで実行した場合)毎行画面に出力する
				//ウグイス描画
				if(settingUguis["isUguis"]) {
					//画像取得
					var image = "";
					image = getImageByKeyword(settingUguis,newest_chat[i]["content"]);
					//画像書き換え
					bg_change(settingUguis,newest_chat[i]["content"],image);
				}
				//棒読みちゃんに読み上げ要求
				BouyomiTalk(newest_chat[i]["content"],iCommand,iSpeed,iTone,iVolume,iVoice,bCode);
				//無音状態まで待機(音声再生中の間はループ)
				do {
					WScript.sleep(ht_setting["TALK_INTERVAL"]);
					iPassTime += ht_setting["TALK_INTERVAL"];
				} while (BouyomiGetNowPlaying()=="01");
			}
		}

		if(settingUguis["isUguis"] && isSilent==false) {
			//前回更新があった場合のみ静止状態に戻す ちらつき防止
			bg_change(settingUguis,"",settingUguis["silent"]);
		}
		isSilent = true;

		//次のポーリング時間まで待機
		interval = ht_setting["POLLING_INTERVAL"]-iPassTime
		if(interval < 0) {
			//読み上げ中にポーリング間隔を過ぎていたら待機しない
			interval=0;
		}
		WScript.sleep(interval);
	}
}



//================================================================
//	最新のチャットを max_line 行取得する
//================================================================
function get_newest_chat(element_chat , max_line) {

	//var str_chat = "";
	var start = 0;
	var loop_end=0;
	var delta= 1;
	var retArray = new Array(); //返却用

	// チャット全体は CHAT_ID で指定したHTMLタグの中で作られていて、
	// 個々の行は CHAT_ITEM_TAG で指定したタグ/クラスで作られている
	var elements_li;
	if(ht_setting["CHAT_ITEM_TYPE"] == "TAG") {
		//タグで検索して取得
		elements_li = element_chat.getElementsByTagName(ht_setting["CHAT_ITEM_TAG"]);
	} else {
		//クラス名で検索して取得
		elements_li = element_chat.getElementsByClassName(ht_setting["CHAT_ITEM_TAG"]);
	}
	//WScript.Echo(element_chat,elements_li,elements_li.length,elements_li.count);

	// HTMLで持っている発言の何番目から取得するか
	if(strDirection == "PUSH_BOTTOM") {
		// 最も古いのが 0 。
		if(elements_li.length-max_line > 0) {
			//取得件数より多くの発言があるなら現在の件数-取得件数
			start = elements_li.length-max_line;
			loop_end = elements_li.length;
		} else {
			//取得件数未満なら最も古いのから取得
			start = 0;
			loop_end = elements_li.length;
		}
	} else {
		// 最も古いのが length-1 。
		if(elements_li.length-max_line > 0) {
			//取得件数より多くの発言があるなら 取得件数
			start = 0;
			loop_end = max_line;
		} else {
			//取得件数未満なら最も古いのから取得
			start = 0;
			loop_end = elements_li.length;
		}
	}

	//チャットを取得する
	for(var i=start;i<loop_end;i++) {
		var str_time="";
		var str_name="";
		var str_content="";
		var set_chat = "";
		var is_search_result = false; //チャット内容に重複があったらtrue

		//セレクタを使用して 時刻 名前 内容 を抽出する
		//サイト別設定フォルダの中の selector.js を使用して
		// str_time str_name str_content を設定します。
		try{
			eval(ht_setting["SELECTOR"]);
		} catch(e) {
			var error_number = e.number & 0xFFFF ;
			// 例外がselector.jsの記載内容に起因する場合は警告表示
			if( (Math.floor(error_number/1000) == 1) || (Math.floor(error_number/1000) == 5) ) {
				var err_str = "selector.js内で例外が発生しました。\n\n"+
								"error number = "+(error_number)+"\n"+
								"error name ="+e.name+"\n"+
								"error message ="+e.message + "\n\n"+
								"selector.js の記載内容を確認してください。\n"+
								"取得しようとしたHTML要素が存在しない場合がないか確認してください。";
				WScript.echo(err_str);
			}
			throw(e);
		}
		//WScript.Echo(i+" "+str_time+" "+str_name+" "+str_content);
		set_chat = {"time":str_time,
					"name":str_name,
					"content":str_content};
		
		//重複判定方法別に重複チェックを行う
		is_search_result = false;
		for(var j=0;j<retArray.length;j++) {
			switch(ht_setting["UNIQUE"]) {
				//内容
				case "CONTENT":
					if(set_chat["content"] == retArray[j]["content"]) {
						is_search_result = true;
					}
					break;
				//名前＋内容
				case "NAMECONTENT":
					if( (set_chat["content"] == retArray[j]["content"]) &&
						(set_chat["name"] == retArray[j]["name"]) ) {
						is_search_result = true;
					}
					break;
				//名前＋時刻＋内容
				case "ALL":
				default:
					if( (set_chat["content"] == retArray[j]["content"]) &&
						(set_chat["name"] == retArray[j]["name"]) &&
						(set_chat["time"] == retArray[j]["time"]) ) {
						is_search_result = true;
					}
					break;
			}
		}
		//重複がないならリストに追加
		if(is_search_result==false) {
			retArray.push(set_chat);
		}
	}

	if(strDirection == "PUSH_TOP") {
		// 上が新しい場合は並び順を逆転させる
		retArray.reverse();
	}

	/*
	for(var i=0;i<retArray.length;i++) {
		WScript.Echo(i+" ["+retArray[i]["time"]+"]["+retArray[i]["name"]+"] "+retArray[i]["content"]);
	}
	WScript.Echo();
	*/
	return retArray;
}

//================================================================
//	最新のチャットと前回のチャットを比較し、更新分となる
//	最新のチャットのインデックスを返す
//	更新がない場合は-1を返す。
//================================================================
function diff_chat( newest_chat , old_chat) {

	//チャット0件の場合は更新なし扱い
	//WScript.echo("newest_chat.length = "+newest_chat.length+" old_chat.length = "+old_chat.length);
	if(newest_chat.length == 0 || old_chat.length == 0) {
		return -1;
	}

	var i=0;
	var str_last = old_chat[old_chat.length-1]["content"]; //前回の最後の発言内容
	var str_last_name = old_chat[old_chat.length-1]["name"]; //前回の最後の発言者
	var str_last_time = old_chat[old_chat.length-1]["time"]; //前回の最後の発言時刻
	var ret_index = 0;
	var is_hit = false; //前回の最後の発言が最新チャットに含まれていればtrue
	//WScript.Echo("最後の発言は",str_last)

	//最新のチャットの古い順に検索する。
	//補足１
	//名前と時刻も検証しないと、「おめー」等の同じ発言が連続で繰り返された場合に
	//正常に更新分を取得できない
	//補足２
	//重いと時刻と名前と内容が必ずしも表示内容と一致しない。
	//内容だけでも判定できるように設定を追加した。
	for(i=0;i<newest_chat.length;i++) {
	//for(i=newest_chat.length-1;i>=0;i--) {
		var is_search_result = false; //一致した発言があった場合にtrue

		//重複判定方法別に検索を行う
		switch(ht_setting["UNIQUE"]) {
			//内容
			case "CONTENT":
				if(str_last == newest_chat[i]["content"]) {
					is_search_result = true;
				}
				break;
			//名前＋内容
			case "NAMECONTENT":
				if( (str_last == newest_chat[i]["content"]) &&
					(str_last_name == newest_chat[i]["name"]) ) {
					is_search_result = true;
				}
				break;
			//名前＋時刻＋内容
			case "ALL":
			default:
				if( (str_last == newest_chat[i]["content"]) &&
					(str_last_name == newest_chat[i]["name"]) &&
					(str_last_time == newest_chat[i]["time"]) ) {
					is_search_result = true;
				}
				break;
		}

		if(is_search_result) {
			ret_index = i+1;
			is_hit = true;
			//WScript.Echo(ret_index+"番目にヒット。"+(ret_index+1)+"番目からが更新分");
			break;
		}
		is_search_result = false;
	}

	//最新チャットの最後の発言と一致しているなら更新なし
	if(ret_index == newest_chat.length) {
		ret_index = -1;
	}
	//ヒットしていないなら最新チャットの内容は全て更新された内容とし
	//newest_chat[0]から最後まで全てが更新分である
	if(is_hit == false) {
		ret_index = 0;
	}
	
	return ret_index;
}




//================================================================
//================================================================
//
// 棒読みちゃん操作関数
//
//================================================================
//================================================================


//================================================================
//	棒読みちゃんに読み上げ要求
//================================================================
function BouyomiTalk(sMessage,iCommand,iSpeed,iTone,iVolume,iVoice,bCode) {

	// ソケット通信のためのBASP21ソケットオブジェクト生成
	var bsocket = WScript.CreateObject("basp21.socket");

	var i = 0;
	var rc1 = 0; //戻り値用
	var byteStreamData = "";

	//入力値チェック
	//★★★未実装★★★

	// 棒読みちゃんに接続
	rc1 = bsocket.Connect(sHostName, iPort, 10)
	if (rc1 != 0 ) {
		alert_popup("棒読みちゃんとの接続に失敗しました\nbsocket戻り値="+rc1);
		//WScript.Quit(-1);
		bsocket.close();
		return;
	}

	byteStreamData	+= toByte(iCommand,lengthData["short"]);
	byteStreamData	+= toByte(iSpeed  ,lengthData["short"]);
	byteStreamData	+= toByte(iTone   ,lengthData["short"]);
	byteStreamData	+= toByte(iVolume ,lengthData["short"]);
	byteStreamData	+= toByte(iVoice  ,lengthData["short"]);
	byteStreamData	+= toByte(bCode   ,lengthData["byte"]);
	byteStreamData	+= toByte(getLength(sMessage),lengthData["int"]);

	var strReq=""; //送信文字列
	//バイト列を16進数の文字列に変換
	for(i=0;i<byteStreamData.length;i++){
		strReq+=("00"+byteStreamData.charCodeAt(i).toString(16)).slice(-2);
	}

	//棒読みちゃんに送信
	rc1 = bsocket.writeHex(strReq);
	if (rc1 != 0 ) {
		alert_popup("棒読みちゃんとへの読み上げコマンド送信(コマンド部)に失敗しました\n"+"送信文字列=["+strReq+"]\nbsocket戻り値="+rc1);
		//WScript.Quit(-1);
		bsocket.close();
		return;
	}
	rc1 = bsocket.writeHex(URItoHex(encodeURIComponent(sMessage)));
	if (rc1 != 0 ) {
		alert_popup("棒読みちゃんへの読み上げコマンド送信(読み上げメッセージ本文)に失敗しました\n"+"送信文字列=["+sMessage+"]\nbsocket戻り値="+rc1);
		//WScript.Quit(-1);
		bsocket.close();
		return;
	}
	bsocket.close();
}

//================================================================
//	状態を取得する
//	取得した状態は16進数文字列で返す
//================================================================
function BouyomiGetNowPlaying() {

	// ソケット通信のためのBASP21ソケットオブジェクト生成
	var bsocket = WScript.CreateObject("basp21.socket");
	var i=0,loop=0;
	var data = ""; //取得した状態 (00:無音状態中, 01:音声再生中)

	// 棒読みちゃんに接続
	rc1 = bsocket.Connect(sHostName, iPort, 10)
	if (rc1 != 0 ) {
		alert_popup("棒読みちゃんとの接続に失敗しました\nbsocket戻り値="+rc1);
		//WScript.Quit(-1);
		bsocket.close();
		return;
	}

	// 状態取得コマンド送信
	var byteStreamData = "";
	byteStreamData	+= toByte(0x0120,lengthData["short"]);

	var strReq=""; //送信文字列
	for(i=0;i<byteStreamData.length;i++){
		strReq+=("00"+byteStreamData.charCodeAt(i).toString(16)).slice(-2);
	}
	rc1 = bsocket.writeHex(strReq);
	if (rc1 != 0 ) {
		alert_popup("棒読みちゃんへの状態取得コマンド送信に失敗しました\n"+"送信文字列=["+strReq+"]\nbsocket戻り値="+rc1);
		//WScript.Quit(-1);
		bsocket.close();
		return;
	}

	data = bsocket.Read2(2);
	bsocket.close();

	return data;
}


//================================================================
//	iData(任意の数値) を iLengthバイトにした数値の配列で返す
//	リトルエンディアン形式
//	例 268,4(32bit数値) → 0x0000010C → 戻る配列の中身は 13,1,0,0
//================================================================
function toByte(iData,iLength) {
	var ret = "";	//戻り値
	var a=0;
	var base=0;		//基数
	var retArray = new Array(); //変換結果の各桁を格納する

	//上位桁を計算
	for(var i = iLength ; i>1;i-- ) {
		base = Math.pow(0x100,i-1);
		a=Math.floor(iData/base);
		retArray[i-1] = a;
		//WScript.echo("iData="+iData,"base="+base,"a="+a);
		iData -= a*base;
	}
	//最後に残った余りを追加
	retArray[0] = iData;

	//数値を文字列に変換
	for(var i=0;i<iLength;i++) {
		if(retArray[i] < 0) {
			//負数は2の補数にする
			ret += String.fromCharCode(0x100+retArray[i]);
		} else {
			//正の数はそのまま
			ret += String.fromCharCode(retArray[i]);
		}
	//WScript.echo("i="+i,retArray[i]);
	}
	return ret;

}

//================================================================
//	文字コードに関係なく文字列のバイト長を求める
//================================================================
function getLength(iString) {
	var ue = "";	// URIエンコード後の文字列
	var per=0;

	ue=encodeURIComponent(iString);
	per=ue.match(/%/g);	// 変換後の全部の%を配列に入れる
	// %xxの３文字で１バイトなのでperの数×2を引いたのが
	// 文字列のバイト数
	if(per!=null) {
		len=(ue.length)-per.length*2;
	} else {
		len=ue.length
	}
	return len;
}


//================================================================
//	URIエンコードされた文字列を16進数の文字列に変換する
//		変換ルール
//		%xx → xx をそのまま渡す
//		上記以外 → 文字コードを先頭0埋めした2文字の16進数にする
//================================================================
function URItoHex(iString) {

	var strRet = "";
	var len = iString.length;

	for(var i=0;i<len;i++) {
		if( iString.charAt(i) != "%" ) {
			// %以外の文字だったら文字コードを16進数にする
			strRet += ("00"+iString.charCodeAt(i).toString(16)).slice(-2);
		} else {
			// %がきたら続く2文字をそのまま渡す
			strRet += iString.charAt(i+1);
			strRet += iString.charAt(i+2);
			i += 2;
		}
	}

	return strRet;
}


//================================================================
//================================================================
//
// ウグイス操作関数
//
//================================================================
//================================================================

//=====================================================
//特定発言に対応した画像ファイルを返す
//=====================================================
function getImageByKeyword(settingUguis,strChat) {
	var i=0;
	for(i=0;i<settingUguis["keyword"].length;i++) {
		//発言の中にキーワードがあるか調べる
		if(strChat.indexOf( settingUguis["keyword"][i]["key"] ) >= 0) {
			//ヒットしたらその発言用の画像を返す
			return settingUguis["keyword"][i]["image"];
		}
	}
	// 通常の発言用画像
	return settingUguis["speak"];
}

//=====================================================
// IE起動 および ウグイス初期設定
//=====================================================
function bootUguisIE(settingUguis) {

	var strURL;

	//ウグイス設定を読み込む
	setUguis(settingUguis);

	//ウグイス表示IEを起動する IEイベントコールバックのプリフィックスは UGUIS_
	var objIE = WScript.CreateObject("InternetExplorer.Application","UGUIS_");
	settingUguis["objUguisIE"]=objIE;

	strURL = "about:blank";
	objIE.Navigate(strURL);

	objIE.Visible = true; //見えないと確認できないのでここだけは Trueにします。
	//objIE.Top = 100;
	//objIE.Left = 100;
	objIE.Width = 250; //250以下は250になる
	objIE.Height = 220;
	objIE.AddressBar = false;	//アドレスバー
	objIE.MenuBar = false;		//メニューバー
	objIE.ToolBar = false;		//ツールバー
	objIE.StatusBar = false;	//ステータスバー
	objIE.Resizable = false;	//サイズ変更不可

	//操作可能になるまで待つ
	waitIE(objIE);
	//初期画面
	bg_init(settingUguis,
				"<b style=\"font-size:larger;color:fuchsia\">棒</b>読みちゃんで<br>○<b style=\"font-size:larger;color:fuchsia\">つの</b>が<br><b style=\"font-size:larger;color:fuchsia\">悲鳴</b>を上げる事案が発声",
				settingUguis["silent"]);

	return objIE;

}

//=====================================================
// IEがビジー状態の間待つ
//=====================================================
function waitIE(ie) {
	do {
		WScript.Sleep(ht_setting["IE_BUSY_INTERVAL"]);
		//WScript.echo("IE Busy...");
	} while((ie.Busy == true) || (ie.readystate != 4))
	WScript.Sleep(ht_setting["IE_BUSY_INTERVAL"]);
}

//=====================================================
// IEの背景画像を書き換える
//=====================================================
function bg_change(settingUguis,strMessage,pathImage) {

	var objWshShell = new ActiveXObject("WScript.Shell");
	var strCurrentDir = objWshShell.CurrentDirectory;
	var strURLprefix=strCurrentDir+"\\"+settingUguis["pathUguis"]+"\\"+settingUguis["strUguisName"]+"\\";

	//画像描画 BODYの背景として表示
	var objIE = settingUguis["objUguisIE"];

	//サニタイズ
	//★★未実装★★
	//strMessage = sanitize(strMessage);

	//チャット内容を表示
	var pathImageForCss = strURLprefix.replace(/\\/g,"/")+"/"+pathImage.replace(/\\/g,"/"); // \を/に置換
	objIE.document.body.style["text-shadow"]="1px 0px white,0px 1px white,0px -1px white";
	objIE.document.body.innerText=strMessage;
	objIE.document.body.style["background-repeat"]="no-repeat";
	objIE.document.body.style["background-image"]="url(file:///"+pathImageForCss+")";
}

//=====================================================
// IEの初期画面生成
//=====================================================
function bg_init(settingUguis,strMessage,pathImage) {

	var objWshShell = new ActiveXObject("WScript.Shell");
	var strCurrentDir = objWshShell.CurrentDirectory;
	var strURLprefix=strCurrentDir+"\\"+settingUguis["pathUguis"]+"\\"+settingUguis["strUguisName"]+"\\";
	var strStyle=" style=\"text-shadow: 1px 0px white,0px 1px white,0px -1px white;bacground-repeat:no-repeat\"";

	//画像描画 BODYの背景として表示
	var strHtmlHeader="<HTML><HEAD><TITLE>ぼつのひめ "+VERSION+"</TITLE></HEAD><BODY background=\""+strURLprefix+pathImage+"\""+ strStyle+">";
	var strHtmlFooter="</BODY></HTML>";

	var objIE = settingUguis["objUguisIE"];

	//初期画面を表示
	objIE.document.open();
	objIE.document.clear();
	if(strMessage!=null) {
		objIE.document.write(strHtmlHeader+strMessage+strHtmlFooter);
	}
	objIE.document.execCommand('BackgroundImageCache', false, true);
	objIE.document.close();
}


//=====================================================
//ウグイスIE終了コールバック
//=====================================================
function UGUIS_OnQuit() {
	//ウグイス表示が閉じられたら終了とみなす
	BouyomiTalk("ぼつのひめを終了します",iCommand,iSpeed,iTone,iVolume,iVoice,bCode);
	//WScript.echo("ウグイス終了を確認。\nぼつの姫を終了します");
	WScript.quit();
}


//=====================================================
//ウグイス設定読込
//=====================================================
function setUguis(settingUguis) {

	//  オープンモード
	var FORREADING      = 1;    // 読み取り専用
	var FORWRITING      = 2;    // 書き込み専用
	var FORAPPENDING    = 8;    // 追加書き込み
	//  開くファイルの形式
	var TRISTATE_TRUE       = -1;   // Unicode
	var TRISTATE_FALSE      =  0;   // ASCII
	var TRISTATE_USEDEFAULT = -2;   // システムデフォルト

	var line_num = 0; //読み込んだファイルの行数
	var keyword = null;
	var keyword_count = 0;

	var fs = WScript.CreateObject( "Scripting.FileSystemObject" );

	if(ht_setting["UGUIS"] != null) {
		settingUguis["strUguisName"] = ht_setting["UGUIS"];
	}

	var date = new Date();
	if((date.getSeconds() % 12 ) == 0) {
		if(fs.FileExists(settingUguis["pathUguis"]+"\\"+settingUguis["strUguisName"]+"_rare\\"+"uguis.txt") == true) {
			settingUguis["strUguisName"] = settingUguis["strUguisName"]+"_rare";
		}
	}

	var file_path = settingUguis["pathUguis"]+"\\"+settingUguis["strUguisName"]+"\\"+"uguis.txt"

	// ファイルの存在チェック
	if( false == fs.FileExists(file_path) ) {
		WScript.Echo("ウグイス設定ファイル\n"+file_path+" が\n見つかりません");
	}

	// ファイルを読み取り専用で開く
	file = fs.OpenTextFile( file_path , FORREADING, false, TRISTATE_FALSE );

	//  ファイルの文字データを一行ずつ読み込む
	settingUguis["keyword"] = new Array();
	while( !file.AtEndOfStream )
	{
		var line;		// ファイルから読み込んで , で分割した文字列の配列

		var strKeyword;	// キーワード(1項目目)
		var strValue;	// 設定値(2項目目)
		var iSpeed;		// 再生スピード
		var iTone;		// 音程
		var iVolume;	// 音量
		var iVoice;		// 声質

		line_num++;

		// 1行取り出して , で分割した配列にする
		line = file.ReadLine().split(",");

		// 先頭が#だったらコメント行扱いにして次の行へ
		// 空行でも次の行へ
		if( line[0].substr(0,1) == "#" || line[0].length == 0) {
			continue;
		}

		// 項目が3項目未満ならエラー
		if( line.length < 2 ) {
			WScript.Echo("ウグイス設定ファイルの "+line_num+"行目の項目数が足りません");
			WScript.quit(-1);
		}

		// 項目を変数に設定＆前後の空白のトリミングする
		strKeyword  = line[0].replace( /(^\s+|\s+$)/g , "" );
		strValue    = line[1].replace( /(^\s+|\s+$)/g , "" );

		switch(strKeyword) {
			//基本設定
			case "silent":
				settingUguis["silent"]=strValue;
				break;
			case "speak":
				settingUguis["speak"]=strValue;
				break;
			case "guild":
				settingUguis["guild"]=strValue;
				break;
			case "system":
				settingUguis["system"]=strValue;
				break;
			//特定発言
			default:
				if(keyword_count >= settingUguis["max_keyword"]) {
					WScript.echo("登録できる特定発言は最大"+settingUguis["max_keyword"]+"件です。");
					WScript.quit(-1);
				}
				keyword={"key":strKeyword,"image":strValue};
				settingUguis["keyword"].push(keyword);
				keyword_count++;
		}
	}

	/*
	var allsetting = "";
	for(var i=0;i<settingUguis["keyword"].length;i++) {
		allsetting += settingUguis["keyword"][i]["key"]+":"+settingUguis["keyword"][i]["image"]+"\n";
	}
	WScript.echo(allsetting);
	*/

	// ファイルを閉じる
	file.Close();
}



//================================================================
//================================================================
//
// 設定操作関数
//
//================================================================
//================================================================

//=====================================================
//設定のクリア
//=====================================================
function clear_setting() {
	ht_setting["SETTING_NAME"] = "";
	ht_setting["URL"] = null;
	ht_setting["DIRECTION"] = "";
	ht_setting["CHAT_ID"] = "";
	ht_setting["CHAT_ITEM_TAG"] = "";
	ht_setting["CHAT_ITEM_TYPE"] = "";
	ht_setting["UNIQUE"] = "";
	ht_setting["UGUIS"] = "";
	ht_setting["FRAME_PARENT"] = "";
	ht_setting["POPUP"] = "";

	ht_setting["POLLING_INTERVAL"] = 0;
	ht_setting["TALK_INTERVAL"] = 0;
	ht_setting["IE_BUSY_INTERVAL"] = 0;
	ht_setting["CHAT_MAX_LINE"] = 0;
	ht_setting["SELECTOR"] = "";
}

//=====================================================
//基本設定の読込
//=====================================================
function read_base_setting() {

	var path="";

	//設定フォルダの存在確認
	path = ht_SETTING_PATH["SETTINGS_DIR"];
	if(checkFolderExist(path) == false) {
		WScript.echo("設定フォルダ "+path+" が見つかりません");
		WScript.quit(-1);
	}
	//基本設定ファイルの存在確認
	path = ht_SETTING_PATH["SETTINGS_DIR"]+"\\"+ht_SETTING_PATH["BASE_SETTING_FILE"];
	if(checkFileExist(path) == false) {
		WScript.echo("基本設定ファイル "+path+" が見つかりません");
		WScript.quit(-1);
	}
	
	//基本設定読込
	set_setting(ht_SETTING_PATH["SETTINGS_DIR"]+"\\"+ht_SETTING_PATH["BASE_SETTING_FILE"]);

}

//=====================================================
//サイト別設定の走査
//読み込まれた設定がある場合はそのフォルダ名を返す
//読み込まれた設定がない場合はnullを返す
//=====================================================
function scan_site_setting(setting_list) {
	var fso, f, fc ;
	var path="";
	var subfolder="";
	var ret_folder=null; //戻り
	var isExistSetting = false; // setting_フォルダが1つもない場合false
	
	fso = WScript.CreateObject("Scripting.FileSystemObject");

	//フォルダ一覧を取得
	//フォルダはフルパスで格納される
	path = ht_SETTING_PATH["SETTINGS_DIR"];
	f = fso.GetFolder(path);
	fc = new Enumerator(f.SubFolders);

	//一覧から接頭語がsetting_のフォルダ名を抜き出す
	for (; !fc.atEnd(); fc.moveNext()) {
		subfolder = fso.GetBaseName(fc.item()); //フルパスからフォルダ名だけを抽出

		//先頭が setting_ のフォルダを探す
		if( subfolder.indexOf(ht_SETTING_PATH["SITE_SETTING_DIR_PREFIX"]) == 0) {
			//WScript.echo(subfolder);
			isExistSetting = true;
			var isDetect = false;
			//まだ読み込まれていないサイト別設定を探す
			for(var i=0;i<setting_list.length;i++) {
				if(setting_list[i] == subfolder) {
					//WScript.echo(subfolder+"読込済み");
					isDetect = true;
					break;
				}
			}
			//読み込まれていないサイト別設定があれば読み込み実行
			if(isDetect == false) {
				ret_folder = subfolder;
				read_site_setting(subfolder);
				setting_list.push(subfolder); //読込済みリスト追加
				break;
			}
		}
	}
	
	if(isExistSetting == false) {
		WScript.echo("サイト別設定フォルダが見つかりません");
		WScript.quit(-1);
	}
	
	return ret_folder;
}


//=====================================================
//サイト別設定の読込
//settings\setting_suffixは存在している前提
//=====================================================
function read_site_setting(setting_path) {
	//WScript.echo("サイト別設定読込 フォルダ名="+setting_path);
	var path="";

	//設定ファイルの存在確認
	path = ht_SETTING_PATH["SETTINGS_DIR"]+"\\"+setting_path+"\\"+ht_SETTING_PATH["SITE_SETTING_FILE"];
	if(checkFileExist(path) == false) {
		WScript.echo("設定フォルダ "+setting_path+" の中に\n"+"設定ファイル"+ht_SETTING_PATH["SITE_SETTING_FILE"]+" が見つかりません");
		WScript.quit(-1);
	}
	//セレクタの存在確認
	path = ht_SETTING_PATH["SETTINGS_DIR"]+"\\"+setting_path+"\\"+ht_SETTING_PATH["SELECTOR"];
	if(checkFileExist(path) == false) {
		WScript.echo("設定フォルダ "+setting_path+" の中に\n"+"チャット選択スクリプト "+ht_SETTING_PATH["SELECTOR"]+" が見つかりません");
		WScript.quit(-1);
	}
	
	//サイト別設定読込
	set_setting(ht_SETTING_PATH["SETTINGS_DIR"]+"\\"+setting_path+"\\"+ht_SETTING_PATH["SITE_SETTING_FILE"]);
	//セレクタ読込
	set_selector(ht_SETTING_PATH["SETTINGS_DIR"]+"\\"+setting_path+"\\"+ht_SETTING_PATH["SELECTOR"]);
}

//=====================================================
//設定読込
//=====================================================
function set_setting(file_path) {

	var fs = WScript.CreateObject( "Scripting.FileSystemObject" );

	//  オープンモード
	var FORREADING      = 1;    // 読み取り専用
	var FORWRITING      = 2;    // 書き込み専用
	var FORAPPENDING    = 8;    // 追加書き込み
	//  開くファイルの形式
	var TRISTATE_TRUE       = -1;   // Unicode
	var TRISTATE_FALSE      =  0;   // ASCII
	var TRISTATE_USEDEFAULT = -2;   // システムデフォルト

	var line_num = 0; //読み込んだファイルの行数

	// ファイルの存在チェック
	if( false == fs.FileExists(file_path) ) {
		WScript.Echo("設定ファイル\n"+file_path+" が\n見つかりません");
	}

	// URLリスト初期化
	ht_setting["URL"] = null;

	// ファイルを読み取り専用で開く
	file = fs.OpenTextFile( file_path , FORREADING, false, TRISTATE_FALSE );

	//  ファイルの文字データを一行ずつ読み込む
	while( !file.AtEndOfStream )
	{
		var line;		// ファイルから読み込んで , で分割した文字列の配列
		var strKeyword;	// キーワード(1項目目)
		var strValue;	// 設定値(2項目目)
		var strOption1	// (3項目目)

		line_num++;

		// 1行取り出して , で分割した配列にする
		line = file.ReadLine().split(",");

		// 先頭が#だったらコメント行扱いにして次の行へ
		// 空行でも次の行へ
		if( line[0].substr(0,1) == "#" || line[0].length == 0) {
			continue;
		}

		// 項目が3項目未満ならエラー
		if( line.length < 2 ) {
			WScript.Echo("設定ファイルの "+line_num+"行目の項目数が足りません");
			WScript.quit(-1);
		}

		// 項目を変数に設定＆前後の空白のトリミングする
		strKeyword  = line[0].replace( /(^\s+|\s+$)/g , "" );
		strValue    = line[1].replace( /(^\s+|\s+$)/g , "" );
		if(line.length >= 3) {
			strOption1  = line[2].replace( /(^\s+|\s+$)/g , "" );
		}

		//項目別に設定
		switch(strKeyword) {
			case "SETTING_NAME":
				ht_setting["SETTING_NAME"]=strValue;
				break;
			case "URL":
				if(ht_setting["URL"] == null) {
					ht_setting["URL"] = new Array();
				}
				ht_setting["URL"].push(strValue);
				break;
			case "FRAME_PARENT":
				ht_setting["FRAME_PARENT"]=strValue;
				break;
			case "DIRECTION":
				ht_setting["DIRECTION"]=strValue;
				break;
			case "CHAT_ID":
				ht_setting["CHAT_ID"]=strValue;
				break;
			case "CHAT_ITEM_TAG":
				ht_setting["CHAT_ITEM_TAG"]=strValue;
				if(strOption1 != null) {
					ht_setting["CHAT_ITEM_TYPE"]=strOption1;
				}
				break;
			case "UNIQUE":
				ht_setting["UNIQUE"]=strValue;
				break;
			case "UGUIS":
				ht_setting["UGUIS"]=strValue;
				break;
			case "POPUP":
				ht_setting["POPUP"]=strValue;
				break;

			case "POLLING_INTERVAL":
				ht_setting["POLLING_INTERVAL"]=strValue;
				break;
			case "TALK_INTERVAL":
				ht_setting["TALK_INTERVAL"]=strValue;
				break;
			case "IE_BUSY_INTERVAL":
				ht_setting["IE_BUSY_INTERVAL"]=strValue;
				break;
			case "CHAT_MAX_LINE":
				ht_setting["CHAT_MAX_LINE"]=strValue;
				break;
			default:
		}
	}

	/*
	var allsetting = "";
	for(var hashkey in ht_setting) {
		allsetting += hashkey + " : "+ht_setting[hashkey]+"\n";
	}
	WScript.echo(allsetting);
	*/

	// ファイルを閉じる
	file.Close();
}



//=====================================================
//セレクタ読込
//=====================================================
function set_selector(file_path) {

	var fs = WScript.CreateObject( "Scripting.FileSystemObject" );
	var selector = "";

	//  オープンモード
	var FORREADING      = 1;    // 読み取り専用
	var FORWRITING      = 2;    // 書き込み専用
	var FORAPPENDING    = 8;    // 追加書き込み
	//  開くファイルの形式
	var TRISTATE_TRUE       = -1;   // Unicode
	var TRISTATE_FALSE      =  0;   // ASCII
	var TRISTATE_USEDEFAULT = -2;   // システムデフォルト

	// ファイルの存在チェック
	if( false == fs.FileExists(file_path) ) {
		WScript.Echo("チャット選択スクリプト"+file_path+" が\n見つかりません");
		WScript.quit(-1);
	}
	if(fs.GetFile(file_path).size == 0) {
		WScript.Echo("チャット選択スクリプト"+file_path+" の\n内容が空です");
		WScript.quit(-1);
	}

	// ファイルを読み取り専用で開く
	file = fs.OpenTextFile( file_path , FORREADING, false, TRISTATE_FALSE );

	//内容読込
	selector = file.ReadAll();
	ht_setting["SELECTOR"]=selector;

	//WScript.echo(selector);

	// ファイルを閉じる
	file.Close();
}



//=====================================================
//ファイルの存在確認
//=====================================================
function checkFileExist(filename) {
	fso = WScript.CreateObject("Scripting.FileSystemObject");
	return fso.FileExists(filename);
}

//=====================================================
//フォルダの存在確認
//=====================================================
function checkFolderExist(fldr) {
	fso = WScript.CreateObject("Scripting.FileSystemObject");
	return fso.FolderExists(fldr);
}

//================================================================
//================================================================
//
// 雑関数
//
//================================================================
//================================================================

//=====================================================
//POPUPメッセージ表示
//=====================================================
function alert_popup(strMessage) {
	switch(ht_setting["POPUP"]) {
		case "0": //表示しない
			break;
		case "10": //表示する
			WScript.echo(strMessage);
			break;
		default: //何もしない
	}
}






