﻿// V2C Sync2ch Script
// B11対応の非公式修正版
// 以下の環境用です
// 　・Rhinoスクリプトエンジン(js-engine.jarとrhino.jar)がなくてもスクリプトを実行できるJava8以降
// 　　　→Java8対応されているV2C-R(2.11.8)、V2C-R+、V2C+が該当
// 　　　→V2C+の場合はサイトで配布されてる公式のv2c_sync.txtでいいのかも
// 　・Java7
// 　・Java6

//【登録場所】 全体
//【ラベル】 Sync2ch同期
//【内容】 sync2ch.com WEBサービスのAPIを利用し、タブ一覧とお気に入り一覧を
//	   サーバーのデーターと同期します。

//【コマンド1】${SCRIPT:A}  v2c_sync.txt 		(Sync2ch同期)
//【コマンド2】${SCRIPT:A}  v2c_sync.txt option  	(同期設定)
//【コマンド3】${SCRIPT:A}  v2c_sync.txt auto  	(自動同期 ON/OFF)
//【コマンド4】${SCRIPT:A}  v2c_sync.txt autoStart  	(起動時自動同期) //StartUpフォルダへ登録
//【コマンド5】${SCRIPT:A}  v2c_sync.txt readAll  	(全タブ既読化)
//【コマンド6】${SCRIPT:A}  v2c_sync.txt deleteLOG		//コマンド1に deleteLOG を追加するとスレ削除同期時にログも削除

//【備考】Sync2ch鯖に登録するurlとSync2ch鯖から取得しV2Cに取り込むurlのプロトコル(http/https)とホスト名のデフォルト
//　　　　　●登録するurl
//　　　　　　・2ch(5ch)   ：https://xxxxx.5ch.net/
//　　　　　　・BBSPINK    ：https://xxxxx.bbspink.com/
//　　　　　　・したらば   ：http://jbbs.shitaraba.net/
//　　　　　　・まちBBS    ：http://xxxxx.machi.to/

//　　　　　●取り込むurl
//　　　　　　・2ch(5ch)   ：http://xxxxx.2ch.net/
//　　　　　　・BBSPINK    ：http://xxxxx.bbspink.com/
//　　　　　　・したらば   ：http://jbbs.shitaraba.net/
//　　　　　　・まちBBS    ：http://xxxxx.machi.to/

//　　　　B11対応の非公式修正版での追加パラメータ(「Sync2ch同期」時のパラメータ)
//　　　　　●同期相手のブラウザのurlの持ち方によって指定する必要あるパラメータ
//　　　　　　・5chHTTP     ：Sync2ch鯖に登録する5chとBBSPINKのurlのプロトコルをhttpにする http://xxxxx.5ch.net/,http://xxxxx.bbspink.com/
//　　　　　　・jbbsHTTPS   ：Sync2ch鯖に登録するしたらばのurlのプロトコルをhttpsにする    https://jbbs.shitaraba.net/
//　　　　　　・machiHTTPS  ：Sync2ch鯖に登録するまちBBSのurlのプロトコルをhttpsにする     https://xxxxx.machi.to/
//　　　　　　・machiNOREG_U：Sync2ch鯖に登録するまちBBSのurlのホスト名を地域を除去した    machi.to にする

//　　　　　●V2C+用のパラメータ
//　　　　　　・5chNATIVE   ：Sync2ch鯖に登録された5chのurlをhttpや2chに変換せずにそのまま取り込む(必須)
//　　　　　　・machiNOREG_D：Sync2ch鯖に登録されたまちBBSのurlのホスト名から地域を除去した machi.to で取り込む

//　　　　　●削除同期でログを物理削除するパラメータ
//　　　　　　・deleteLOG   ：スレ削除の同期時にログも削除する(スレ一覧の既読マークも消えます)、B11.25.4以降が必要

//【更新日時】2019/03/03 ログを削除するパラメータ(deleteLOG、B11.25.4以降が必要)を追加
//　　　　　　2019/03/01 B11対応の非公式修正
//　　　　　　　　　　　 同期設定の適用後、自動で同期処理をしないように修正(公式のv2c_sync.txtに挙動を合わせる)
//　　　　　　2019/02/28 B11対応の非公式修正(Java8以降＋Rhinoスクリプトエンジンの環境では使用不能)

var CLIENT_NAME = "V2C Sync";
var CLIENT_VERSION = "3.1.2"; 
var CONF_FILENAME = "conf.txt"; //設定ファイル名
var CLIENT_ID_FILENAME = "client_id.txt"
var COUNT_DATA_FILENAME = "data.txt";
var POST_DATA_FILENAME = "posts.txt";
var AUTO_PROPERTY_NAME = "key_auto_session";
var MIN_SYNC_SPAN_MINUTES = 3;//自動同期間隔制限
var DEFAULT_SYNC_SPAN_MINUTES = 10;//デフォルト自動同期間隔
var DEBUG = false;
var SAVE_LOG = false;
var USESSL = true;

var ARG_OPTION = 'option'; //設定画面起動
var ARG_UNREAD_ALL = 'readAll';//全タブ既読化
var ARG_TOGGLE_AUTO = 'auto'; //自動同期トグル
var ARG_AUTOSTART = 'autoStart'; //自動同期開始
var ARG_DEBUG = 'debug';
var ARG_LOG = "log";
var ARG_NOSSL = "nossl";

var REQ_5CH_HTTP    = false;
var REQ_JBBS_HTTPS  = false;
var REQ_MACHI_HTTPS = false;
var RES_5CH_NATIVE  = false;
var REQ_MACHI_NOREG = false;
var RES_MACHI_NOREG = false;
var DELETELOG       = false;

var ARG_REQ_5CH_HTTP    = '5chHTTP';
var ARG_REQ_JBBS_HTTPS  = 'jbbsHTTPS';
var ARG_REQ_MACHI_HTTPS = 'machiHTTPS';
var ARG_RES_5CH_NATIVE  = '5chNATIVE';
var ARG_REQ_MACHI_NOREG = 'machiNOREG_U';
var ARG_RES_MACHI_NOREG = 'machiNOREG_D';
var ARG_DELETELOG       = 'deleteLOG';

function main() {
    DEBUG = hasArg( ARG_DEBUG );
    SAVE_LOG = hasArg( ARG_LOG );
    USESSL = !hasArg ( ARG_NOSSL );

    REQ_5CH_HTTP    = hasArg( ARG_REQ_5CH_HTTP );
    REQ_JBBS_HTTPS  = hasArg( ARG_REQ_JBBS_HTTPS );
    REQ_MACHI_HTTPS = hasArg( ARG_REQ_MACHI_HTTPS );
    RES_5CH_NATIVE  = hasArg( ARG_RES_5CH_NATIVE );
    REQ_MACHI_NOREG = hasArg( ARG_REQ_MACHI_NOREG );
    RES_MACHI_NOREG = hasArg( ARG_RES_MACHI_NOREG );
    DELETELOG       = hasArg( ARG_DELETELOG );

    if (hasArg( ARG_UNREAD_ALL )) {
        markAllTabsAsRead();
        return;
    }

    if (hasArg( ARG_AUTOSTART )) {
        startAutoSync();
        return;
    }

    if (hasArg( ARG_TOGGLE_AUTO ) ) { 
        if (v2c.getProperty(AUTO_PROPERTY_NAME)) {
            v2c.removeProperty(AUTO_PROPERTY_NAME);
            v2c.println("自動同期ストップリクエスト");
            v2c.context.setStatusBarText("自動同期終了");
        } else {
            startAutoSync();
        }
        return;
    }

    if (hasArg( ARG_OPTION )) {
        startConfigure();
        return;
    }

    trySync(true);
}

function hasArg(arg) {
    var args = v2c.context.args;
    for (var i = 0; i < args.length; i++) {
        if (args[i] == arg) {
            return true;
        }
    }
    return false;
}

function debug(str) {
    if (DEBUG)
        v2c.println(str);
}

function startAutoSync() {
    var thisSessionKey = "SK"+Math.random(); //多重起動防止用セッションキー
    v2c.putProperty(AUTO_PROPERTY_NAME, thisSessionKey);

    while(true) {
        var currentSession = v2c.getProperty(AUTO_PROPERTY_NAME);
        if (!currentSession || currentSession != thisSessionKey) {
            v2c.setStatus("自動同期終了");
            v2c.context.setStatusBarText("自動同期終了");
            v2c.println("自動同期終了");
            return;
        }

        v2c.setStatus("自動同期開始");
        v2c.println("自動同期開始");

        var conf = getConfObject();
        threadRepository.clear();

        try {
            doSync(conf);
        } catch (e) {
            var message = "■エラー(自動同期中) " + e.lineNumber+ "行目: "+ e.toString(); 
            v2c.println(message);
            v2c.context.setStatusBarText(message);
        }

        var span = new Number(conf.span);
        if (isNaN(span) || MIN_SYNC_SPAN_MINUTES > span) 
            span = MIN_SYNC_SPAN_MINUTES;//最低3分
        java.lang.Thread.sleep(span*60000);//60秒*1000ms
    }
}

function markAllTabsAsRead() {
    var all = getAllTabThreads();
    for (var i = 0; i < all.length; i++) {
        var th = all[i];
        th.resetUnread();
        th.clearNewMark()
    }
}

function getAllTabThreads () {
    var threads = [];
    for(var colIndex = 0; colIndex < v2c.resPane.columnCount; colIndex++) {
        var column = v2c.resPane.columns[colIndex];

        for(var tabIndex = column.tabCount - 1; tabIndex >= 0; tabIndex--) {
            var th = column.threads[tabIndex];
            if (th != null) {
                threads.push(th);
            }
        }
    }
    return threads;
}

//設定ファイルを読み取り、同期を試みます。
function trySync(showOptionScreenIfFailed, conf) {
    if (conf == null)
        conf = getConfObject();

    if (conf && conf.id && conf.password) {
        doSync(conf); //同期を開始
    } else if (showOptionScreenIfFailed) {
        startConfigure();
    }
}

function doSync(conf) {
    var id  = conf.id;
    var pass = conf.password;

    localCountData.open();
    postData.open();

    //同期番号の読み込み
    var syncNumber = 0;
    var syncNumFile = v2c.getScriptDataFile("sync_number.txt");
    if (syncNumFile.exists()) {
        try {
            var str = v2c.readFile(syncNumFile);
            syncNumber = java.lang.Integer.parseInt(str);
            v2c.println("同期番号(送信):"+syncNumber);
        } catch(e) {
            v2c.println("有効な同期番号を読み取れません。0で初期化します。");
        }
    }
    //クライアント番号の読み込み
    var clientId  = 0;
    var clientIdFile = v2c.getScriptDataFile(CLIENT_ID_FILENAME);
    if (clientIdFile.exists()) {
        try {
            var str = v2c.readFile(clientIdFile);
            clientId = java.lang.Integer.parseInt(str);
            v2c.println("クライアント番号:"+clientId);
        } catch(e) {
            v2c.println("有効なクライアント番号を読み取れません。0で初期化します。");
        }
    }

    var url = new java.net.URL( (USESSL?"https":"http")+"://"+getHost()+"/api/sync3");

    var conn = url.openConnection();
    conn.setDoOutput(true);// POST
    var useCompression = true;
    if (useCompression)
        conn.setRequestProperty("Encoding", "gzip");
    conn.setRequestProperty("Accept-Encoding", "gzip");
    conn.setRequestProperty("Authorization",
            "Basic " + Base64.encode(id+":"+pass));
    conn.setRequestProperty("User-Agent", CLIENT_NAME +" "+ CLIENT_VERSION);

    var os = conn.getOutputStream();
    if (useCompression) 
        os = new java.util.zip.GZIPOutputStream(os);
    //var writer = ;
    var bw = new java.io.BufferedWriter(new java.io.OutputStreamWriter(os, "utf-8"));


    Crypt.setKey(conf.cryptLevel, conf.cryptPass);


    // リクエストXMLの作成
    var xml = createRequestXml(syncNumber, clientId, conf);
    xml = replaceRequestXML(xml);
    if (SAVE_LOG) saveSentXML(xml);
    debug(xml);

    bw.write(new java.lang.String(xml));
    bw.close();

    // レスポンスXML受信
	var isGZIP = false;
    var inputStream = conn.getInputStream();

    var encHeader = conn.getHeaderField("Content-Encoding");
    if (encHeader && encHeader+"" == "gzip") {
		isGZIP = true;
        inputStream = new java.util.zip.GZIPInputStream(inputStream);
    }

    var text = replaceResponseXML(inputStream, conn, isGZIP);
    var jxml = new java.lang.String(text);
    inputStream = new java.io.ByteArrayInputStream(jxml.getBytes("utf-8"));

    //DOM
    var factory = javax.xml.parsers.DocumentBuilderFactory.newInstance();
    var builder = factory.newDocumentBuilder();
    var doc = builder.parse(inputStream);

    inputStream.close();
    conn.disconnect();

    var clientId = attr(doc.documentElement, "client_id");
    if (clientId != null) {
        v2c.writeStringToFile(clientIdFile, clientId+"");
    }

    var recievedSyncNumber = attr(doc.documentElement, "sync_number");
    var remainCount = attr(doc.documentElement, "remain");
    var remainStr =  remainCount == "-1" ?"":  " | 残り同期回数:"+remainCount+ "回";
    var accountType = attr(doc.documentElement, "account_type");
    if (recievedSyncNumber) {
        var message = "同期完了(#"+ recievedSyncNumber +" | "+accountType+remainStr+") ["+ new Date().toLocaleTimeString()+"]";
        //解析Main
        parseResponseDocument(conf, doc, function(){
            v2c.writeStringToFile(syncNumFile, recievedSyncNumber+"");

            v2c.println("同期番号(受信):"+recievedSyncNumber);
            v2c.println(message);
            v2c.setStatus(message);
            v2c.context.setStatusBarText(message);
        });
    }
    threadRepository.clear();

}

function saveSentXML(xml) {
    try {
        var logFile = v2c.getScriptDataFile("sent_xml_log.txt");
        var str = logFile.exists() ? v2c.readFile(logFile)+"" : "";
        if (str.length > 500000) {
            str = str.substring(0, 470000);
        }
        var dateStr = "\n["+ new Date().toLocaleTimeString()+"]";
        str = dateStr + "\n" + xml +"\n"+ str + "\n";
        v2c.writeStringToFile(logFile, str);
    } catch(e) {
        v2c.println("ログ保存エラー");
    }

}

function getHost() {
    var args = v2c.context.args;
    for (var i = 0; i < args.length; i++) {
        var arg = args[i];
        if (arg.length() > 5 && arg.substring(0,5) == "host:") {
            return arg.substring(5);
        }
    }
    return "sync2ch.com";
}

function canSyncThread(th) { //同期できるスレッドかどうか
    return th && !th.local && !th.bbs.twitter;
}

//newMarkResIndexとviewResIndexの初期化のためタブを開いていく
function initAllTabThreads () {
    var first = true;
    for(var colIndex = 0; colIndex < v2c.resPane.columnCount; colIndex++) {
        var column = v2c.resPane.columns[colIndex];
        var selectedThread = column.selectedThread ; 

        for(var tabIndex = column.tabCount - 1; tabIndex >= 0; tabIndex--) {
            var th = column.threads[tabIndex];
            if (th != null) {
                if (0 == th.viewResIndex) {
                    column.openThread(th, false, false, false);
                    if (first == true) {
                        java.lang.Thread.sleep(600);
                        first = false;
                    }
                }
            }
        }

        if (selectedThread) {
            column.openThread(selectedThread, false, false, false);
        }
    }
}





function createRequestXml(syncNumber, clientId, conf) {
    var os = escapeXML(java.lang.System.getProperty("os.name"));
    var syncPosts = conf.post ? " sync_rl=\"post\"": "";
    var cryptLevel = conf.cryptLevel > 0 ? " crypt_level=\""+conf.cryptLevel+"\"" : "";

    var xml = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n";
    xml += "<sync2ch_request client_id=\""+clientId+"\" client_name=\""+CLIENT_NAME+"\" client_version=\""
        +CLIENT_VERSION+"\" sync_number=\""+syncNumber+"\" os=\""+os+"\""+syncPosts+cryptLevel+">\n";


    var entityIndex = 0;
    var idToThreads = {}; // id -> th
    var idToBoards = {};
    var urlToIds = {}; //url -> id


    var initFinished = "InitFinished";
    var shouldInit = "true" != v2c.getProperty(initFinished);
    if (shouldInit) {
        initAllTabThreads();
        v2c.putProperty(initFinished,"true");
    }

    //タブ一覧
    if (conf.tab) {
        var column  = getSyncTabColumn(conf);
        if (column) {
            var idList = [];
            for(var tabIndex = 0; tabIndex < column.tabCount; tabIndex++) {
                var th = column.getThread(tabIndex);


                if (canSyncThread(th)) {
                    threadRepository.putIfMissing(th);
                    var id = urlToIds[th.url];
                    if (id == undefined) {
                        id = urlToIds[th.url] = entityIndex;
                        idToThreads[id] = th;
                        entityIndex++;
                    } 

                    idList.push(id);
                }
            }
            xml += "<thread_group abbr=\"true\" struct=\"default\" category=\"open\" id_list=\""
                +idList.join(',')+"\" />\n";
        }
    }

    // お気に入り一覧
    if (conf.fav) {
        var hasDirChild = function(item) {
            for(var i = 0; i < item.childCount; i++) {
                var child = item.getChild(i);
                if (!child.thread && !child.board && child.label) {
                    return true;
                }
            }
            return false;
        };
        var addChilds = function(tagName, attrArray, folder) {
            var useIdList = !hasDirChild(folder);

            var innerXML = "";
            var idList = useIdList ? [] : null;

            for (var i = 0; i < folder.childCount; i++) {
                var child = folder.getChild(i);

                if (child.thread) {
                    var th = child.thread;
                    threadRepository.putIfMissing(th);

                    if (canSyncThread(th)) {
                        var id = urlToIds[th.url];
                        if (id == undefined) {
                            id = urlToIds[th.url] = entityIndex;
                            idToThreads[entityIndex] = th;
                            entityIndex++;
                        }
                        if (useIdList) {
                            idList.push(id);
                        } else {
                            innerXML += "<th id=\""+ id +"\"/>\n";
                        }
                    }
                }  
                else if (child.board) {
                    var id = urlToIds[child.board.url];
                    if (id == undefined) {
                        id = urlToIds[child.board.url] = entityIndex;
                        idToBoards[entityIndex] = child.board;
                        entityIndex++;
                    }
                    if (useIdList) {
                        idList.push(id);
                    } else {
                        innerXML += "<bd id=\""+ id+"\"/>\n";
                    }
                }
                else if (child.label) {
                    innerXML += addChilds('dir', {'name':Crypt.encFolder(child.label)}, child);
                }
            }

            if (useIdList) {
                attrArray['id_list'] = idList.join(',');
                return makeElement(tagName, attrArray, null);
            } else {
                return makeElement(tagName, attrArray, innerXML);
            }
        };


        var targetFavTab = getFavTabForSync(conf);
        if (targetFavTab) {
            var favCate = "favorite";
            var favStructId =  "default";
            xml += addChilds("thread_group", {'struct': favStructId, 'abbr':'true', 'category':favCate}, targetFavTab.root);
        }

    }

    xml += "<entities>\n";
    for(var id in idToThreads) {
        xml += threadToXml(conf, idToThreads[id], id);
    }
    for (var id in idToBoards) {
        var bd = idToBoards[id];
        var url = fixBoardUrl(bd.url+"");
        xml += "<bd id=\""+id +"\" title=\""+Crypt.encTitle(bd.name, url)+"\" "
            + "url=\""+Crypt.encUrl(url)+"\" />";
    }
    xml += "</entities>\n";


    xml += "</sync2ch_request>";

    return xml;
}

function escapeXML(str) {
    str = str + '';
    return str.replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;');
}


function makeElement(tagName, attrArray, innerXML) {
    if (innerXML) {
        var xml = "<" + tagName + arrayToAttributes(attrArray) +">\n";
        xml += innerXML;
        xml += "</" + tagName + ">\n";
        return xml;
    } else {
        return "<" + tagName + arrayToAttributes(attrArray) +"/>\n";
    }
}

//配列からXMLの属性表現を生成
function arrayToAttributes(assocArray) {
    var result = "";
    for (var i in assocArray) {
        result += " " + i + "=\"" + assocArray[i] + "\"";
    }
    return result;
}

function threadToXml(conf, th, id) {
    var attrs = [];
    attrs['title'] = Crypt.encTitle(th.title, th.url);
    attrs['url'] = Crypt.encUrl(th.url);
    attrs['id'] = id;

    var resCount = th.localResCount > th.resCount ? th.localResCount : th.resCount;
    if (resCount > 1) {
        attrs['count'] = Crypt.encCount(resCount, th.url);
        if (localCountData.getCount(th.url)) { //dat落ち対策
            if (localCountData.getCount(th.url) > resCount) {
                attrs['count'] = Crypt.encCount(localCountData.getCount(th.url), th.url);
            }
        }

        if (th.unread) {
            //未読時には現在位置まで既読済みにする。
            if (th.viewResIndex > th.newMarkResIndex) {
                th.newMarkResIndex = th.viewResIndex + 1;
            }
            attrs['read'] = Crypt.encRead(th.newMarkResIndex, th.url);
        } else {
            if (th.localResCount && th.resCount && th.localResCount < th.resCount) {
                //未取得のレスがある場合
                attrs['read'] = Crypt.encRead(th.localResCount, th.url);

            } else {
                attrs['read'] = Crypt.encRead(resCount, th.url);
            }
        }

        attrs['now'] = Crypt.encNow(1 + th.viewResIndex, th.url);
    }

    if (conf.post) {
        var posts = [];
        for (var i = 0; i < th.postResIndex.length; i++) {
            var idx = 1+Number(th.postResIndex[i]);
            posts.push(idx);
        }
        var reservedPosts = postData.getPosts(th.url);
        if (reservedPosts) {
            for(var i = 0; i < reservedPosts.length; i++) {
                posts.push(reservedPosts[i]);
            }
        }

        if (posts.length > 0) {
            attrs['rl_post'] = Crypt.encPostNums(posts, th.url);//.join(',');
            //debug(attrs['rl_post']);
        }
    }

    return "\t<th"+ arrayToAttributes(attrs) + "/>\n";
}


//属性がある場合のみ、JavaScriptの文字列にして返す。
function attr(el, name) {
    if (!el || !name) return undefined;

    var attrNode = el.attributes.getNamedItem(name);
    if (attrNode) {
        return "" + attrNode.nodeValue;
    }

    return undefined;
}


function ThreadInfo () {
    //this.thread = null;
}

ThreadInfo.fromElement = function(node) {
    var info = new ThreadInfo();

    info.isBoard = node.nodeName+"" == "bd";
    info.url = Crypt.decUrl(attr(node, 'url'));
    info.title = Crypt.decTitle(attr(node, 'title'), info.url);
    info.count = Crypt.decCount(attr(node, 'count'), info.url);
    info.status = attr(node, 's');
    info.readPos = Crypt.decRead(attr(node, 'read'), info.url);
    info.readingPos = Crypt.decNow(attr(node, 'now'), info.url);
    info.id = attr(node, 'id');
    var posts = attr(node, "rl_post");
    if (posts != undefined) {
        var tempPosts = (posts == "") ? [] : Crypt.decPostNums(posts, info.url);
        info.posts = [];
        for (var i in tempPosts) {
            info.posts.push(tempPosts[i]);
        }
    }

    return info;
}


var threadRepository = {
    map : {}
          ,clear : function() {
              delete this.map;
              this.map = {};
          }
          ,put : function (url, th) {
              this.map[url] = th;
          }
          ,get : function  (url) {
              return this.map[url];
          }
          ,contains: function(url) {
              return this.map[url] !== undefined;
          }
          ,putIfMissing : function(th) {
              this.map[th.url] = th;
          }
};

var updateManager = { updateThreadUrls : {}
    ,selectedThread : null
        , allThreads : {}
    , column: null
        , allTabThreads: null
        , init : function(conf) {
            this.column = getSyncTabColumn(conf);
            delete this.allThreads;
            this.allThreads  = {};
        }

    , saveSelectedThread:  function() {
        this.selectedThread = this.column.selectedThread;
    }
    , restoreSelectedThread : function() {
        if (this.selectedThread) {
            this.column.openThread(this.selectedThread, false, false, false);
        }
    }
    , notifyRemoveSelectedThread: function(th) {
        if (this.selectedThread && this.selectedThread.url+"" == th.url+"") {
            this.selectedThread = null;
        }
    }
    , addThread : function(th, threadInfo) {
        if (threadInfo.status == 'n')  {
            return;
        }

        if (!this.allThreads[th.url]) {
            threadInfo.thread = th;
            this.allThreads[th.url] = threadInfo;

        }
    }

    , applyPosts : function (th, threadInfo) {
        if (threadInfo.posts) {
            var shouldAdd = threadInfo.status == 'a';

            var reservePosts = []; //予備データ保存用
            debug("posts = "+ threadInfo.title + ": " + threadInfo.posts.join(","));
            for (var i = 0; i < threadInfo.posts.length; i++) {
                try {
                    var res = th.getRes(threadInfo.posts[i]-1);
                } catch(e) {
                    v2c.println("error in th.getRes" + (threadInfo.posts[i]));
                }
                if (res && th.postResLabel) {
                    res.setResLabel(th.postResLabel);
                } else {
                    reservePosts.push(threadInfo.posts[i]);
                }
            }

            if (reservePosts.length > 0) {
                postData.setPosts(th.url, th.title, reservePosts);
            } else if (!shouldAdd) {
                postData.deletePosts(th.url);
            }

            if (!shouldAdd) {
                //消去
                var removeResList = [];
                for (var i = 0; i < th.postResIndex.length; i++) {
                    var resNumber = 1 + Number(th.postResIndex[i]);
                    if (threadInfo.posts.indexOf(''+resNumber) == -1) {
                        var res = th.getRes(th.postResIndex[i]);
                        if (res) {
                            removeResList.push(res);
                        }
                    }
                }
                for (var i in removeResList) {
                    removeResList[i].setResLabel(null);
                }
            }
        }

    }

    , isThOpenInTab  : function (th) {
        for (var i = 0; i < this.allTabThreads.length; i++) {
            var tabTh = this.allTabThreads[i];
            if (th == tabTh || th.url+"" == tabTh.url+"") {
                return true;
            }
        }
        return false;
    }
    , updateThreads : function (conf) {
        this.allTabThreads = getAllTabThreads();

        //既読化
        for(var url in this.allThreads) {
            var threadInfo = this.allThreads[url];
            var th = threadInfo.thread;

            var localResCount = th.localResCount;
            var readingPos = Number(threadInfo.readingPos);

            if (threadInfo.readPos > 0 && th.newMarkResIndex != threadInfo.readPos) {
                th.newMarkResIndex = threadInfo.readPos;
                if (localResCount <= threadInfo.readPos) {
                    localCountData.deleteCount(th.url);
                    if (th.unread) { 
                        th.clearNewMark();
                    }
                }
            }
        }


        var lazyUpdateThreads  = [];

        //更新・
        for(var url in this.allThreads) {
            var threadInfo = this.allThreads[url];
            var th = threadInfo.thread;

            var localResCount = th.localResCount;
            var readingPos = Number(threadInfo.readingPos);

            var tempOpenMode =  (this.isThOpenInTab(th) == false);
            var isOpenTweakThread = false;

            if (localResCount < threadInfo.readPos
                    || localResCount < threadInfo.count) {
                //未取得レスが想定されるので更新。
                if (th.unread) {
                    localCountData.addThreadUrl(th.url, threadInfo.count);
                }

                var success = th.updateAndWait(); 
                if (success) {
                    localCountData.deleteCount(th.url); //更新に成功したらカウント消去
                } 

                if (tempOpenMode == false) {
                    th.newMarkResIndex = threadInfo.readPos;
                    if (th.localResCount <= threadInfo.readPos) {
                        th.clearNewMark();
                    }
                } else {
                    isOpenTweakThread = true;
                }
            } 

            this.applyPosts(th, threadInfo);

            if ( readingPos > 0 && th.viewResIndex != readingPos - 1) {
                if (tempOpenMode) {
                    isOpenTweakThread = true;
                } else {
                    try {
			//v2c.println(th.viewResIndex+"[]:"+(readingPos-1));
                        th.viewResIndex  = readingPos - 1;
                    } catch(ex) {
                        v2c.println("error occured while setting th.viewResIndex");
                    }
                }
            }

            if (isOpenTweakThread) {
                lazyUpdateThreads.push(threadInfo);
            }
        }

        //お気に入りのスレッドを開きながら既読・現在位置変更
        for (var i in lazyUpdateThreads) {
            var threadInfo = lazyUpdateThreads[i];
            var th = threadInfo.thread;
            var localResCount = th.localResCount;
            var readingPos = Number(threadInfo.readingPos);

	    //th = v2c.getThread(th.url+"", threadInfo.title);
            this.column.openThread(th, false, true, true);
            //java.lang.Thread.sleep(300);
	    //th = v2c.getThread(th.url+"", threadInfo.title);
            //java.lang.Thread.sleep(300);
	    

            th.newMarkResIndex = threadInfo.readPos;
            if (th.localResCount <= threadInfo.readPos) {
                th.clearNewMark();
            }

            //v2c.println(th.viewResIndex+":"+readingPos);
            if (th.unread == false) { //read == true
                th.close();
                this.column.openThread(th, false, true, false);
                java.lang.Thread.sleep(100);
            }
            if (readingPos > 0 && th.viewResIndex != readingPos - 1) {
                try {
                    th.viewResIndex  = readingPos - 1;
                } catch(ex) {
                    v2c.println("error occured while setting th.viewResIndex");
                }
            }

            java.lang.Thread.sleep(200);

            if (!(conf.remainTab && threadInfo.status =='a')) {
                th.close();
            }
        }

        localCountData.deleteMissingThreads(this.allThreads);
        localCountData.save();
        postData.save();

    }
};


//書き込みラベルを適切に設定できなかった時のためのデータ
var postData = {
    // "http://dummy.2ch.net/test/read.cgi/bar/132112438/" : { "title": "Thread Title",  "posts" : [313, 532]  }
    map : {}
          ,open : function() {
              var postFile = v2c.getScriptDataFile(POST_DATA_FILENAME);
              if (postFile.exists()) {
                  try {
                      var dataText = v2c.readFile(postFile);
                      this.map = eval(dataText+"");
		      if (this.map == null) {
			      //v2c.println(dataText);
			      this.map = {};
		      }
                  } catch (e) {
		      if (this.map == null) {
			  this.map = {};
		      }
                      v2c.println(POST_DATA_FILENAME+ "読み込みエラー");
                  }
              }

          }
          ,save : function() {
              var dataFile = v2c.getScriptDataFile(POST_DATA_FILENAME);
              var str = "";
              for(var url in this.map) {
                  var entry = this.map[url];
                  if (entry["posts"] && entry["posts"].length > 0) {
                      var joined = entry["posts"].join();
                      if (joined != '') {
                          str += '"' + url +'":{"title":"'+entry.title+'", "posts":['+joined+"]},\n\t";
                      }
                  }
              }
              v2c.writeStringToFile(dataFile, "({\n\t"+str+"\n})");
          }
          ,getPosts : function (url) {
              return this.map[url] ?  this.map[url]["posts"] : null;
          }
          ,setPosts: function (url, title, posts) {
              this.map[url] = {"title": title, "posts" : posts};
          }
          ,deletePosts : function (url, posts) {
              delete this.map[url];
          }

};

//Dat落ち対策のためのカウント数保持
var localCountData =  {
    map : {}
          ,open : function () {
              var dataFile = v2c.getScriptDataFile(COUNT_DATA_FILENAME);
              if (dataFile.exists()) {
                  try {
                      var dataText = v2c.readFile(dataFile);
                      this.map = eval(dataText+"");
		      if (this.map == null) {
			      //v2c.println(dataText);
			      this.map = {};
		      }
                  }catch(e) {
		      if (this.map == null) {
			  this.map = {};
		      }
                      v2c.println(COUNT_DATA_FILENAME+ "読み込みエラー");
                  }
              }
          }
          ,save : function () {
              var dataFile = v2c.getScriptDataFile(COUNT_DATA_FILENAME);
              var str = "";
              for(var i in this.map) {
                  str += '"' + i +'":'+this.map[i]+",\n\t";
              }
              v2c.writeStringToFile(dataFile, "({\n\t"+str+"\n})");
          }
          ,addThreadUrl : function(url, count) {
              this.map[url] = count;
          }
          ,getCount : function(url) {
              return this.map[url];
          }
          ,deleteCount : function(url) {
              delete this.map[url];
          }
          ,deleteMissingThreads : function(list) {
              //listに無いエントリを削除する
              for(var i in this.map) {
                  if (!list[i]) {
                      delete this.map[i];
                  }
              }
          }
};

function parseResponseDocument(conf, doc, onParseFinished) {
    updateManager.init(conf);

    var topLevelNodes = doc.documentElement.childNodes;
    var entities = {}; //id -> threadInfo
    for(var i = 0; i < topLevelNodes.length; i++) {
        var groupNode = topLevelNodes.item(i);
        if (!(groupNode instanceof org.w3c.dom.Element)) {
            continue;
        }
        var tagName = ''+groupNode.nodeName;

        if (tagName == "entities") {
            for (var j = 0; j < groupNode.childNodes.length ; j++) {
                var node = groupNode.childNodes.item(j);
                if (!(node instanceof org.w3c.dom.Element) ) {
                    continue;
                }

                var tagName = node.nodeName+"";

                var threadInfo = ThreadInfo.fromElement(node) ;
                entities[threadInfo.id] = threadInfo;

                if (threadInfo.isBoard == false) {
                    var th = null;
                    var url = threadInfo.url + '';
                    if (threadRepository.contains(url)) {
                        th = threadRepository.get(url);
                    } else {
                        th = v2c.getThread(url, threadInfo.title);
                        if (th) {
                            debug(url+","+ threadInfo.title);
                            threadRepository.putIfMissing( th);
                        }
                    }
                    if (th) {
                        updateManager.addThread(th, threadInfo);
                    }
                }
            }

        } else if (tagName == "thread_group") {
            var status = attr(groupNode, 's');
            if (status == "n") continue;


            var category = attr(groupNode, 'category');
            var tabCategory = "open";
            var favCategory = "favorite";

            if (category == tabCategory) { //タブ一覧
                parseTabGroupElement(conf, groupNode, entities);
            } else if (category == favCategory) { //お気に入り一覧
                var targetFavoriteTab = getFavTabForSync(conf);

                if (targetFavoriteTab) {
                    allThreads = getAllThreads(targetFavoriteTab, targetFavoriteTab.root);
                    insertFavRecurse(targetFavoriteTab, groupNode, targetFavoriteTab.root, allThreads, entities);
                }
            }
        }

    }

    if (onParseFinished) {
        onParseFinished(); //時間がかかる更新の前に同期番号を保存しておく。
    }

    updateManager.updateThreads(conf);
}



function parseTabGroupElement(conf, groupNode, entities) {
    var column = getSyncTabColumn(conf);
    if (!column) return;

    var selectedTh = column.selectedThread;

    var skippedThreads = {};
    var allThreads = {};
    for (var tabIndex = column.tabCount - 1; tabIndex >= 0; tabIndex--) {
        var th = column.threads[tabIndex];
        if (th) {
            allThreads[th.url+""] = th;
        }
    }

    idList = [];
    var urlMap = {};
    var collectThList = function(element) {
        for (var j = 0; j < element.childNodes.length ; j++) {
            var node = element.childNodes.item(j);
            if ((node instanceof org.w3c.dom.Element) && node.nodeName+"" == "dir") {
                collectThList(node);
                continue;
            }

            if (!(node instanceof org.w3c.dom.Element) || node.nodeName+"" != "th") {
                continue;
            } 

            var idAttr = attr(node, 'id');
            if (idAttr == undefined || !entities[idAttr]) 
                continue;


            var threadInfo = entities[idAttr] ;
            urlMap[threadInfo.url+""] = threadInfo;
            idList.push(threadInfo);
        }
    };

    collectThList(groupNode);

    //削除
    for (var url in allThreads) {
        if (!urlMap[url]) {
            var th = allThreads[url];
            if (canSyncThread(th)) {
                if (selectedTh) {
                    if (th == selectedTh || th.url+"" == selectedTh.url+"") {
                        selectedTh = null;
                    }
                }
                th.close();
                if(DELETELOG) v2c.readURL((''+th.url).replace('://', '://DELETE.').replace(/$/, '?#DELETE#'));
            }

        }
    }

    var insertIndex = 0;
    if (idList != null)
        for (var j = 0; j < idList.length ; j++) {
            var threadInfo = idList[j] ;

            //挿入可能位置まで進める
            insertIndex = proceedInsertIndex(column, insertIndex);

            var sameThread = false;
            var th = allThreads[threadInfo.url];

            if (threadRepository.contains(threadInfo.url)) {
                th = threadRepository.get(threadInfo.url);
            } else if (th == null) {
                th = v2c.getThread(threadInfo.url, threadInfo.title);
            }
            if (th== null) {
                continue;
            }

            if (insertIndex < column.tabCount) {
                var existTh = column.threads[insertIndex];
                if (existTh.url+'' == threadInfo.url) { //一致
                    sameThread = true;
                } 
            }

            if (!sameThread) {
                if (th.columnIndex == -1) {
                    column.openThread(th, false, true, true);
                }

                th.movePanelTo(conf.tabColumn, insertIndex >= column.tabCount ? -1 : insertIndex);
            }

            insertIndex++;
        }

    for(var k  = column.tabCount - 1 ; k >= insertIndex; k--) {
        var th = column.threads[k];
        if (canSyncThread(th))
            column.threads[k].close();
    }

    if (selectedTh) {
        column.openThread(selectedTh, false, false, false);
    }

}

function proceedInsertIndex(column, insertIndex) {
    for(var tabIndex = insertIndex; tabIndex < column.tabCount; tabIndex++) {
        var th = column.threads[tabIndex];
        if (canSyncThread(th)) {
            return tabIndex;
        }
    }
    return column.tabCount;
}

/**
 * お気に入りには複数のスレッドを登録できないので、
 * 削除時のため、全てのスレッドを参照しておく。
 */
function getAllThreads(favTab, folder) {
    var threads = {};
    for (var c = 0; c < folder.childCount; c++) {
        var child = folder.getChild(c);
        if (child.thread) {
            threads[child.thread.url+""] = child;
        }else if (child.board) {
            threads[child.board.url+""] = child;
        } else if (child.childCount > 0) {
            var childThreads = getAllThreads(favTab, child);
            for(var url in childThreads) {
                threads[url] = childThreads[url];
            }
        }
    }

    return threads;
}

function fixBoardUrl(boardName) {
    return boardName.replace("shitaraba.com", "livedoor.jp");
}

/**
 * お気に入りを受け取ったXMLの順番で格納していく再帰関数
 * フォルダはinsertItem関数を使うと閉じてしまうため対策が必要。
 */
function insertFavRecurse(favTab, groupNode, folder, removedAllThreads, entities) {
    var folders = {};
    var threads = {};
    var deleteFolders = {};
    var deleteThreads = {};
    var deleteBoards = {};


    //ハッシュマップの生成  url->thread, label->folder
    //と消去アイテムの削除
    if (folder.childCount > 0) {
        for (var c = folder.childCount-1; c >= 0; c--) {
            var child = folder.getChild(c);
            if (child) { 
                if (child.thread){
                    threads[child.thread.url+""] = child.thread;
                    deleteThreads[child.thread.url+""] = child.thread;
                } else if (child.board) {
                    threads[child.board.url+""] = child.board;
                    deleteBoards[fixBoardUrl(child.board.url+"")] = child.board;
                } else if (child.label){ //フォルダ
                    folders[child.label+""] = child;
                    deleteFolders[child.label+""] = child;
                }
            }
        }
    }


    var targetList = [];//groupNode.childNodes;

    for (var j=0; j < groupNode.childNodes.length ; j++) {
        var node = groupNode.childNodes.item(j);

        if (!(node instanceof org.w3c.dom.Element)) {
            continue;
        }


        if (node.nodeName+"" == "dir") {
            var name = Crypt.decFolder(attr(node, "name"));
            targetList.push({tagName:'dir', "name": name, element: node});
            delete deleteFolders[name];
        } else if (node.nodeName+"" == "th") {
            var idAttr = attr(node, "id");
            var url = entities[idAttr].url;
            delete deleteThreads[url];
            targetList.push({tagName:'th', "id": idAttr});
        } else if (node.nodeName+"" == "bd") {
            var idAttr = attr(node, "id");
            var url = entities[idAttr].url;//attr(node, "url");
            targetList.push({tagName:'bd', "id": idAttr});
            delete deleteBoards[url];
        }
    }
    for(var i in deleteBoards) {
        favTab.removeItem(deleteBoards[i]);
        delete threads[i];
    }
    for(var i in deleteThreads) {
        var th = deleteThreads[i];
        if (canSyncThread(th)) {
            favTab.removeItem(th);
            delete threads[i];
        }
    }
    for(var i in deleteFolders) {
        favTab.removeItem(deleteFolders[i]);
        delete folders[i];
    }


    //挿入と並び替え
    var insertIndex = 0;
    for (var j=0; j < targetList.length ; j++) {
        var node = targetList[j];


        var currentChild = null;
        if (insertIndex < folder.childCount) {
            currentChild = folder.getChild(insertIndex);
        }

        if (node.tagName+"" == "dir") {
            var name = node.name;
            var childFolder = null;

            if (currentChild && currentChild.label+"" == name  && !currentChild.thread && !currentChild.board) {
                childFolder  = currentChild; //現並びと一致
            } else if (folders[name]) {
                favTab.insertItem(folder, folders[name], insertIndex);
                childFolder = folders[name];
            }  else {
                //新規フォルダ
                childFolder = favTab.insertFolder(folder, name, insertIndex);
            }
            insertIndex++;

            if (childFolder && attr(node.element, 's') != "n") {
                insertFavRecurse(favTab, node.element, childFolder, removedAllThreads, entities);
            }

        } else if (node.tagName+"" == "th") {
            var idAttr = node.id;
            if (idAttr == undefined || !entities[idAttr]) {
                continue;
            }

            var threadInfo = entities[idAttr];

            var url = threadInfo.url + "";

            if (currentChild && currentChild.thread && currentChild.thread.url+"" == url) {
                delete threads[url];

            } else {
                if (removedAllThreads[url]) {
                    favTab.removeItem(removedAllThreads[url]);
                    delete removedAllThreads[url];
                }

                var th = null;
                if (threads[url]) {
                    th = threads[url];
                    favTab.removeItem(threads[url]);
                } else if (threadRepository.contains(url)) {
			th = threadRepository.get(url);
		} else if (th == null) {
			th = v2c.getThread(url, threadInfo.title);
		}

                if (th) {
                    favTab.insertItem(folder, th, insertIndex);
                }
            }
            insertIndex++;
        } else if (node.name+"" == "bd") {
            var idAttr = node.id;

            if (idAttr == undefined || !entities[idAttr]) {
                continue;
            }

            var boardInfo = entities[idAttr];

            var url = boardInfo.url;
            if (currentChild && currentChild.board && currentChild.board.url+"" == url) {
                delete threads[url];
            } else {
                if (removedAllThreads[url]) {
                    favTab.removeItem(removedAllThreads[url]);
                    delete removedAllThreads[url];
                }

                var bd = null;
                if (threads[url]) {
                    bd = threads[url];
                    favTab.removeItem(threads[url]);
                } else {
                    bd = v2c.getBoard(url);
                }
                if (bd) {
                    favTab.insertItem(folder, bd, insertIndex);
                }
            }
            insertIndex++;
        }
    }

}


//設定画面を起動
function startConfigure() {
    v2c.context.setPopupHTML(generateConfHTML());
    v2c.context.setTrapFormSubmission(true);
    v2c.context.setPopupFocusable(true);
}

function generateConfHTML() {
    var conf = getConfObject();
    var noConf = conf == null; //初期起動時にはこの変数を用いてデフォルト値を設定する。

    var id = 	noConf ? "" : conf.id;
    var pass =	noConf ? "" : conf.password;
    var tab =	noConf ? true : (conf.tab ? true : false);
    var fav  = 	noConf ? true : (conf.fav ? true : false);
    var post =	noConf ? false : (conf.post ? true : false);
    var remainTab =	noConf ? false : (conf.remainTab==undefined?false:(conf.remainTab ? true : false));
    var favTab =	noConf ? -1 : conf.favTab;
    var tabColumn = noConf ? -1 : conf.tabColumn;
    var cryptLevel = (noConf || conf.cryptLevel == undefined)? 0 : conf.cryptLevel;
    var cryptPass = (noConf || conf.cryptPass == undefined)? "" : conf.cryptPass;

    var span = (noConf || isNaN(conf.span)) ? DEFAULT_SYNC_SPAN_MINUTES : conf.span;

    var html = '<html><head><style>td{font-size:10px; margin-top:1px; padding-top:0px;padding-bottom:0px} td.label{text-align:right}</style></head>';
    html += '<body style="margin:7px">';
    html += "<center><h1 style=\"font-size:19px; font-family:'Lucida Grande', 'Hiragino Kaku Gothic ProN', 'ヒラギノ角ゴ ProN W3', Meiryo, メイリオ, sans-serif;\">Sync2ch同期設定</h1>";
    html += '<div>'+CLIENT_NAME+ ' ' +CLIENT_VERSION+' (<a href="http://sync2ch.com/?c=v">http://sync2ch.com</a>)</div>';
    html += '<form action="GET">';


    html += '<table style="margin-top:4px; margin-bottom:4px">';

    html += '<tr>';
    html += '<td class="label">ID : </td>';
    html += '<td><input size="15" type="text" value="'+id+'" name="id"/></td>';
    html += '</tr>';

    html += '<tr>';
    html += '<td class="label">API接続用パスワード : </td>';
    html += '<td><input size="15" type="password" value="'+pass+'" name="password"/></td>';
    html += '</tr>';

    html += '<tr>';
    html += '<td class="label">タブ一覧を同期する : </td>';
    html += '<td><input type="checkbox" '+ (tab ? "checked":"" )+' name="tab"/></td>';
    html += '</tr>';

    html += '<tr>';
    html += '<td class="label">お気に入りを同期する : </td>';
    html += '<td><input type="checkbox" ' + (fav ? "checked" : "")+' name="fav"/></td>';
    html += '</tr>';

    html += '<tr>';
    html += '<td class="label">書き込んだレス番号を同期する : <br/>(プレミアムアカウントのみ) &nbsp; </td>';
    html += '<td><input type="checkbox" ' + (post ? "checked" : "")+' name="post"/></td>';
    html += '</tr>';

    html += '<tr ><td colspan="2"><hr/></td></tr>';

    html += '<tr>';
    html += '<td class="label">新規スレをタブに表示 : <br/>(現在位置の正確な同期のため) &nbsp; </td>';
    html += '<td><input type="checkbox" ' + (remainTab ? "checked" : "")+' name="remainTab"/></td>';
    html += '</tr>';

    html += '<tr>';
    html += '<td class="label">同期用タブカラム : </td>';
    html += '<td><select name="tabColumn">';

    for(var colIndex = 0; colIndex < v2c.resPane.columnCount; colIndex++) {
        var column = v2c.resPane.columns[colIndex];
        var shortText = (1+colIndex)+': ';
        for(var i in column.threads) {
            shortText += column.threads[i].title;
            if (shortText.length > 16) {
                shortText = shortText.substring(0, 16)+"..." ;
            }
            if (column.tabCount > 1) {
                shortText += "(他"+ (column.tabCount-1)+")";
            }
            break;
        }

        var selectAttr = colIndex == tabColumn ? "selected" : "";

        html += '<option '+selectAttr+' value="'+colIndex+'">'+shortText+'</option>';
    }
    html += '</select></td>';
    html += '</tr>';


    html += '<tr>';
    html += '<td class="label">同期用お気に入りタブ : </td>';
    html += '<td><select name="favTab">';
    for (var k = 0; k < v2c.favorites.count ; k++) {
        var fav = v2c.favorites.getFavorite(k);
        var selectAttr = (k == favTab) ? "selected": "";
        html += '<option value="'+k+'" '+selectAttr+'>'+(k+1)+": "+fav.name+'</option>';
    }
    html += '</select></td>';
    html += '</tr>';


    //html += '<tr ><td colspan="2"><hr/></td></tr>';

    html += '<tr>';
    html += '<td class="label">同期データ暗号化 : </td>';
    html += '<td><select name="cryptLevel" onselect="alert(0)">';
    html += '<option value="0" '+(cryptLevel==0?"selected":"")+'>使用しない</option>';
    html += '<option value="1" '+(cryptLevel==1?"selected":"")+'>レベル1 (書き込み番号のみ暗号化)</option>';
    html += '<option value="2" '+(cryptLevel==2?"selected":"")+'>レベル2 (1+スレッドのタイトルを暗号化)</option>';
    html += '<option value="3" '+(cryptLevel==3?"selected":"")+'>レベル3 (2+スレッドのURLを暗号化)</option>';
    html += '<option value="4" '+(cryptLevel==4?"selected":"")+'>レベル4 (3+フォルダー名を暗号化)</option>';
    html += '<option value="5" '+(cryptLevel==5?"selected":"")+'>レベル5 (4+レス数や既読番号を暗号化</option>';

            html += '</select></td>';
            html += '</tr>';
            html += '<tr>';
            html += '<td class="label">暗号化用パスワード : </td>';
            html += '<td><input size="15" type="password" value="'+cryptPass+'" name="cryptPass"/></td>';
            html += '</tr>';

            html += '<tr>';
            html += '<td class="label">自動同期間隔(分) : </td>';
            html += '<td><input size="15" type="text" value="'+span+'" name="span"/></td>';
            html += '</tr>';

            //html += '<tr ><td colspan="2"><hr/></td></tr>';

            html += '</table>';


            html += '<br/><div><input type="submit" value="適用"/>';
            html += '&nbsp;&nbsp;<input type="submit" value="キャンセル" name="cancel"/>';
            html += '</div>';
            html += '</form>';

            html += '</body>';
            html += '</html>';
            return html;
}


function formSubmitted(u,sm,sd) {
    var prefs = sd.split('&');

    var confText = "";
    var isCanceled = false;

    var id = null;
    var pass = null;

    for(var i in prefs) {
        var nameAndValue = prefs[i].split('=');
        var name = nameAndValue[0];
        var value = nameAndValue[1];

        if (name == 'cancel') {
            isCanceled = true;
            break;
        } 

        if (name == 'id') {
            id = value;
            value = e(value);
        } else if (name == 'password'){
            pass = value;
            value = e(value);
        } else if (name == 'cryptPass') {
            value = e(value);
        } else {
            value = "\""+value+"\"";
        }

        confText += name+':'+value+",\n\t";
        debug(confText);

    }

    if (!isCanceled) {
        var idFile = v2c.getScriptDataFile(CONF_FILENAME);
        v2c.writeStringToFile(idFile, "({\n\t"+confText+"\n})");
    }

    v2c.context.closeOriginalPopup();

    //同期再開
    if (!isCanceled && !hasArg(ARG_OPTION) && id && pass) {
        trySync(false);
    }
}

//設定オブジェクトを取得
function getConfObject() {
    var idFile = v2c.getScriptDataFile(CONF_FILENAME);
    if (idFile.exists()) {
        var confText = v2c.readFile(idFile);
	v2c.println(confText);
        var conf = eval(confText+"");
        if (conf) {
            conf.password = d(conf.password);
            conf.id = d(conf.id);
            conf.cryptPass = d(conf.cryptPass);
            return conf;
        }
    }
    return null;
}

function getFavTabForSync(conf) {
    var targetFavTab = v2c.favorites.getFavorite(conf.favTab);
    if (!targetFavTab) {
        for (var i = 0; i < v2c.favorites.count ; i++) {
            targetFavTab = v2c.favorites.getFavorite(i);
            break;
        }
    }

    return targetFavTab;
}


function getSyncTabColumn(conf) {
    var column  = null;
    var columnCount = v2c.resPane.columnCount;

    if (conf.tabColumn < columnCount) {
        column = v2c.resPane.columns[conf.tabColumn];
    } else if (columnCount > 0) {
        conf.tabColumn = 0;
        column = v2c.resPane.columns[0];
    }

    return column;
}

function e(value) {
    var sksSpec = new javax.crypto.spec.SecretKeySpec(key().getBytes(), "Blowfish");
    var cipher = javax.crypto.Cipher.getInstance("Blowfish");
    cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, sksSpec);

    var fin  = cipher.doFinal(new java.lang.String(value).getBytes());
    var arr = [];
    for (var i = 0; i < fin.length; i++) {
		arr[i] = fin[i];
    }
    var str =  "[" + arr.join(',') + "]";
    //v2c.println(str);
    return str;
}

function key() {
    var p = "I3";
    var ver = java.lang.System.getProperty("os.version")+"";
    for(var i = 0; i <  ver.length && i < 3; i++) p+=ver[i];
    var ver = java.lang.System.getProperty("user.name")+"";
    for(var i = 0; i <  ver.length && i < 4; i++) p+=ver[i];

    return new java.lang.String(p+"c");
}

function d(value) {
    try {
		//v2c.println("value = " + value);
		//v2c.println(v2c.scriptEngineName);
		if ((''+v2c.scriptEngineName).indexOf('Nashorn') != -1) {
                    
			var javaArray = Java.to(value,"byte[]");
		} else {
			var javaArray = new Packages.java.lang.reflect.Array.newInstance(java.lang.Byte.TYPE, value.length);
			for(var i = 0; i < value.length; i++) {
				javaArray[i] = value[i];
			}
		}

		//print(JavaArray[0]+JavaArray1+JavaArray[2]);
	
        var sksSpec = new javax.crypto.spec.SecretKeySpec(key().getBytes(), "Blowfish");

        var cipher = javax.crypto.Cipher.getInstance("Blowfish");
        cipher.init(javax.crypto.Cipher.DECRYPT_MODE, sksSpec);

		var temp = cipher.doFinal(javaArray);
		//v2c.println(temp);
        return ""+new java.lang.String(temp); 
    } catch(e) {
    	v2c.println(e.message);
        return "";
    }
}


/*
   CryptoJS v3.1.2
   code.google.com/p/crypto-js
   (c) 2009-2013 by Jeff Mott. All rights reserved.
   code.google.com/p/crypto-js/wiki/License
   */
var CryptoJS=CryptoJS||function(u,p){var d={},l=d.lib={},s=function(){},t=l.Base={extend:function(a){s.prototype=this;var c=new s;a&&c.mixIn(a);c.hasOwnProperty("init")||(c.init=function(){c.$super.init.apply(this,arguments)});c.init.prototype=c;c.$super=this;return c},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var c in a)a.hasOwnProperty(c)&&(this[c]=a[c]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.init.prototype.extend(this)}},
    r=l.WordArray=t.extend({init:function(a,c){a=this.words=a||[];this.sigBytes=c!=p?c:4*a.length},toString:function(a){return(a||v).stringify(this)},concat:function(a){var c=this.words,e=a.words,j=this.sigBytes;a=a.sigBytes;this.clamp();if(j%4)for(var k=0;k<a;k++)c[j+k>>>2]|=(e[k>>>2]>>>24-8*(k%4)&255)<<24-8*((j+k)%4);else if(65535<e.length)for(k=0;k<a;k+=4)c[j+k>>>2]=e[k>>>2];else c.push.apply(c,e);this.sigBytes+=a;return this},clamp:function(){var a=this.words,c=this.sigBytes;a[c>>>2]&=4294967295<<
        32-8*(c%4);a.length=u.ceil(c/4)},clone:function(){var a=t.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var c=[],e=0;e<a;e+=4)c.push(4294967296*u.random()|0);return new r.init(c,a)}}),w=d.enc={},v=w.Hex={stringify:function(a){var c=a.words;a=a.sigBytes;for(var e=[],j=0;j<a;j++){var k=c[j>>>2]>>>24-8*(j%4)&255;e.push((k>>>4).toString(16));e.push((k&15).toString(16))}return e.join("")},parse:function(a){for(var c=a.length,e=[],j=0;j<c;j+=2)e[j>>>3]|=parseInt(a.substr(j,
            2),16)<<24-4*(j%8);return new r.init(e,c/2)}},b=w.Latin1={stringify:function(a){var c=a.words;a=a.sigBytes;for(var e=[],j=0;j<a;j++)e.push(String.fromCharCode(c[j>>>2]>>>24-8*(j%4)&255));return e.join("")},parse:function(a){for(var c=a.length,e=[],j=0;j<c;j++)e[j>>>2]|=(a.charCodeAt(j)&255)<<24-8*(j%4);return new r.init(e,c)}},x=w.Utf8={stringify:function(a){try{return decodeURIComponent(escape(b.stringify(a)))}catch(c){throw Error("Malformed UTF-8 data");}},parse:function(a){return b.parse(unescape(encodeURIComponent(a)))}},
    q=l.BufferedBlockAlgorithm=t.extend({reset:function(){this._data=new r.init;this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=x.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var c=this._data,e=c.words,j=c.sigBytes,k=this.blockSize,b=j/(4*k),b=a?u.ceil(b):u.max((b|0)-this._minBufferSize,0);a=b*k;j=u.min(4*a,j);if(a){for(var q=0;q<a;q+=k)this._doProcessBlock(e,q);q=e.splice(0,a);c.sigBytes-=j}return new r.init(q,j)},clone:function(){var a=t.clone.call(this);
        a._data=this._data.clone();return a},_minBufferSize:0});l.Hasher=q.extend({cfg:t.extend(),init:function(a){this.cfg=this.cfg.extend(a);this.reset()},reset:function(){q.reset.call(this);this._doReset()},update:function(a){this._append(a);this._process();return this},finalize:function(a){a&&this._append(a);return this._doFinalize()},blockSize:16,_createHelper:function(a){return function(b,e){return(new a.init(e)).finalize(b)}},_createHmacHelper:function(a){return function(b,e){return(new n.HMAC.init(a,
                e)).finalize(b)}}});var n=d.algo={};return d}(Math);
(function(){var u=CryptoJS,p=u.lib.WordArray;u.enc.Base64={stringify:function(d){var l=d.words,p=d.sigBytes,t=this._map;d.clamp();d=[];for(var r=0;r<p;r+=3)for(var w=(l[r>>>2]>>>24-8*(r%4)&255)<<16|(l[r+1>>>2]>>>24-8*((r+1)%4)&255)<<8|l[r+2>>>2]>>>24-8*((r+2)%4)&255,v=0;4>v&&r+0.75*v<p;v++)d.push(t.charAt(w>>>6*(3-v)&63));if(l=t.charAt(64))for(;d.length%4;)d.push(l);return d.join("")},parse:function(d){var l=d.length,s=this._map,t=s.charAt(64);t&&(t=d.indexOf(t),-1!=t&&(l=t));for(var t=[],r=0,w=0;w<
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     l;w++)if(w%4){var v=s.indexOf(d.charAt(w-1))<<2*(w%4),b=s.indexOf(d.charAt(w))>>>6-2*(w%4);t[r>>>2]|=(v|b)<<24-8*(r%4);r++}return p.create(t,r)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="}})();
(function(u){function p(b,n,a,c,e,j,k){b=b+(n&a|~n&c)+e+k;return(b<<j|b>>>32-j)+n}function d(b,n,a,c,e,j,k){b=b+(n&c|a&~c)+e+k;return(b<<j|b>>>32-j)+n}function l(b,n,a,c,e,j,k){b=b+(n^a^c)+e+k;return(b<<j|b>>>32-j)+n}function s(b,n,a,c,e,j,k){b=b+(a^(n|~c))+e+k;return(b<<j|b>>>32-j)+n}for(var t=CryptoJS,r=t.lib,w=r.WordArray,v=r.Hasher,r=t.algo,b=[],x=0;64>x;x++)b[x]=4294967296*u.abs(u.sin(x+1))|0;r=r.MD5=v.extend({_doReset:function(){this._hash=new w.init([1732584193,4023233417,2562383102,271733878])},
    _doProcessBlock:function(q,n){for(var a=0;16>a;a++){var c=n+a,e=q[c];q[c]=(e<<8|e>>>24)&16711935|(e<<24|e>>>8)&4278255360}var a=this._hash.words,c=q[n+0],e=q[n+1],j=q[n+2],k=q[n+3],z=q[n+4],r=q[n+5],t=q[n+6],w=q[n+7],v=q[n+8],A=q[n+9],B=q[n+10],C=q[n+11],u=q[n+12],D=q[n+13],E=q[n+14],x=q[n+15],f=a[0],m=a[1],g=a[2],h=a[3],f=p(f,m,g,h,c,7,b[0]),h=p(h,f,m,g,e,12,b[1]),g=p(g,h,f,m,j,17,b[2]),m=p(m,g,h,f,k,22,b[3]),f=p(f,m,g,h,z,7,b[4]),h=p(h,f,m,g,r,12,b[5]),g=p(g,h,f,m,t,17,b[6]),m=p(m,g,h,f,w,22,b[7]),
        f=p(f,m,g,h,v,7,b[8]),h=p(h,f,m,g,A,12,b[9]),g=p(g,h,f,m,B,17,b[10]),m=p(m,g,h,f,C,22,b[11]),f=p(f,m,g,h,u,7,b[12]),h=p(h,f,m,g,D,12,b[13]),g=p(g,h,f,m,E,17,b[14]),m=p(m,g,h,f,x,22,b[15]),f=d(f,m,g,h,e,5,b[16]),h=d(h,f,m,g,t,9,b[17]),g=d(g,h,f,m,C,14,b[18]),m=d(m,g,h,f,c,20,b[19]),f=d(f,m,g,h,r,5,b[20]),h=d(h,f,m,g,B,9,b[21]),g=d(g,h,f,m,x,14,b[22]),m=d(m,g,h,f,z,20,b[23]),f=d(f,m,g,h,A,5,b[24]),h=d(h,f,m,g,E,9,b[25]),g=d(g,h,f,m,k,14,b[26]),m=d(m,g,h,f,v,20,b[27]),f=d(f,m,g,h,D,5,b[28]),h=d(h,f,
            m,g,j,9,b[29]),g=d(g,h,f,m,w,14,b[30]),m=d(m,g,h,f,u,20,b[31]),f=l(f,m,g,h,r,4,b[32]),h=l(h,f,m,g,v,11,b[33]),g=l(g,h,f,m,C,16,b[34]),m=l(m,g,h,f,E,23,b[35]),f=l(f,m,g,h,e,4,b[36]),h=l(h,f,m,g,z,11,b[37]),g=l(g,h,f,m,w,16,b[38]),m=l(m,g,h,f,B,23,b[39]),f=l(f,m,g,h,D,4,b[40]),h=l(h,f,m,g,c,11,b[41]),g=l(g,h,f,m,k,16,b[42]),m=l(m,g,h,f,t,23,b[43]),f=l(f,m,g,h,A,4,b[44]),h=l(h,f,m,g,u,11,b[45]),g=l(g,h,f,m,x,16,b[46]),m=l(m,g,h,f,j,23,b[47]),f=s(f,m,g,h,c,6,b[48]),h=s(h,f,m,g,w,10,b[49]),g=s(g,h,f,m,
                E,15,b[50]),m=s(m,g,h,f,r,21,b[51]),f=s(f,m,g,h,u,6,b[52]),h=s(h,f,m,g,k,10,b[53]),g=s(g,h,f,m,B,15,b[54]),m=s(m,g,h,f,e,21,b[55]),f=s(f,m,g,h,v,6,b[56]),h=s(h,f,m,g,x,10,b[57]),g=s(g,h,f,m,t,15,b[58]),m=s(m,g,h,f,D,21,b[59]),f=s(f,m,g,h,z,6,b[60]),h=s(h,f,m,g,C,10,b[61]),g=s(g,h,f,m,j,15,b[62]),m=s(m,g,h,f,A,21,b[63]);a[0]=a[0]+f|0;a[1]=a[1]+m|0;a[2]=a[2]+g|0;a[3]=a[3]+h|0},_doFinalize:function(){var b=this._data,n=b.words,a=8*this._nDataBytes,c=8*b.sigBytes;n[c>>>5]|=128<<24-c%32;var e=u.floor(a/
                    4294967296);n[(c+64>>>9<<4)+15]=(e<<8|e>>>24)&16711935|(e<<24|e>>>8)&4278255360;n[(c+64>>>9<<4)+14]=(a<<8|a>>>24)&16711935|(a<<24|a>>>8)&4278255360;b.sigBytes=4*(n.length+1);this._process();b=this._hash;n=b.words;for(a=0;4>a;a++)c=n[a],n[a]=(c<<8|c>>>24)&16711935|(c<<24|c>>>8)&4278255360;return b},clone:function(){var b=v.clone.call(this);b._hash=this._hash.clone();return b}});t.MD5=v._createHelper(r);t.HmacMD5=v._createHmacHelper(r)})(Math);
(function(){var u=CryptoJS,p=u.lib,d=p.Base,l=p.WordArray,p=u.algo,s=p.EvpKDF=d.extend({cfg:d.extend({keySize:4,hasher:p.MD5,iterations:1}),init:function(d){this.cfg=this.cfg.extend(d)},compute:function(d,r){for(var p=this.cfg,s=p.hasher.create(),b=l.create(),u=b.words,q=p.keySize,p=p.iterations;u.length<q;){n&&s.update(n);var n=s.update(d).finalize(r);s.reset();for(var a=1;a<p;a++)n=s.finalize(n),s.reset();b.concat(n)}b.sigBytes=4*q;return b}});u.EvpKDF=function(d,l,p){return s.create(p).compute(d,
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      l)}})();
CryptoJS.lib.Cipher||function(u){var p=CryptoJS,d=p.lib,l=d.Base,s=d.WordArray,t=d.BufferedBlockAlgorithm,r=p.enc.Base64,w=p.algo.EvpKDF,v=d.Cipher=t.extend({cfg:l.extend(),createEncryptor:function(e,a){return this.create(this._ENC_XFORM_MODE,e,a)},createDecryptor:function(e,a){return this.create(this._DEC_XFORM_MODE,e,a)},init:function(e,a,b){this.cfg=this.cfg.extend(b);this._xformMode=e;this._key=a;this.reset()},reset:function(){t.reset.call(this);this._doReset()},process:function(e){this._append(e);return this._process()},
    finalize:function(e){e&&this._append(e);return this._doFinalize()},keySize:4,ivSize:4,_ENC_XFORM_MODE:1,_DEC_XFORM_MODE:2,_createHelper:function(e){return{encrypt:function(b,k,d){return("string"==typeof k?c:a).encrypt(e,b,k,d)},decrypt:function(b,k,d){return("string"==typeof k?c:a).decrypt(e,b,k,d)}}}});d.StreamCipher=v.extend({_doFinalize:function(){return this._process(!0)},blockSize:1});var b=p.mode={},x=function(e,a,b){var c=this._iv;c?this._iv=u:c=this._prevBlock;for(var d=0;d<b;d++)e[a+d]^=
    c[d]},q=(d.BlockCipherMode=l.extend({createEncryptor:function(e,a){return this.Encryptor.create(e,a)},createDecryptor:function(e,a){return this.Decryptor.create(e,a)},init:function(e,a){this._cipher=e;this._iv=a}})).extend();q.Encryptor=q.extend({processBlock:function(e,a){var b=this._cipher,c=b.blockSize;x.call(this,e,a,c);b.encryptBlock(e,a);this._prevBlock=e.slice(a,a+c)}});q.Decryptor=q.extend({processBlock:function(e,a){var b=this._cipher,c=b.blockSize,d=e.slice(a,a+c);b.decryptBlock(e,a);x.call(this,
                e,a,c);this._prevBlock=d}});b=b.CBC=q;q=(p.pad={}).Pkcs7={pad:function(a,b){for(var c=4*b,c=c-a.sigBytes%c,d=c<<24|c<<16|c<<8|c,l=[],n=0;n<c;n+=4)l.push(d);c=s.create(l,c);a.concat(c)},unpad:function(a){a.sigBytes-=a.words[a.sigBytes-1>>>2]&255}};d.BlockCipher=v.extend({cfg:v.cfg.extend({mode:b,padding:q}),reset:function(){v.reset.call(this);var a=this.cfg,b=a.iv,a=a.mode;if(this._xformMode==this._ENC_XFORM_MODE)var c=a.createEncryptor;else c=a.createDecryptor,this._minBufferSize=1;this._mode=c.call(a,
                    this,b&&b.words)},_doProcessBlock:function(a,b){this._mode.processBlock(a,b)},_doFinalize:function(){var a=this.cfg.padding;if(this._xformMode==this._ENC_XFORM_MODE){a.pad(this._data,this.blockSize);var b=this._process(!0)}else b=this._process(!0),a.unpad(b);return b},blockSize:4});var n=d.CipherParams=l.extend({init:function(a){this.mixIn(a)},toString:function(a){return(a||this.formatter).stringify(this)}}),b=(p.format={}).OpenSSL={stringify:function(a){var b=a.ciphertext;a=a.salt;return(a?s.create([1398893684,
                        1701076831]).concat(a).concat(b):b).toString(r)},parse:function(a){a=r.parse(a);var b=a.words;if(1398893684==b[0]&&1701076831==b[1]){var c=s.create(b.slice(2,4));b.splice(0,4);a.sigBytes-=16}return n.create({ciphertext:a,salt:c})}},a=d.SerializableCipher=l.extend({cfg:l.extend({format:b}),encrypt:function(a,b,c,d){d=this.cfg.extend(d);var l=a.createEncryptor(c,d);b=l.finalize(b);l=l.cfg;return n.create({ciphertext:b,key:c,iv:l.iv,algorithm:a,mode:l.mode,padding:l.padding,blockSize:a.blockSize,formatter:d.format})},
                        decrypt:function(a,b,c,d){d=this.cfg.extend(d);b=this._parse(b,d.format);return a.createDecryptor(c,d).finalize(b.ciphertext)},_parse:function(a,b){return"string"==typeof a?b.parse(a,this):a}}),p=(p.kdf={}).OpenSSL={execute:function(a,b,c,d){d||(d=s.random(8));a=w.create({keySize:b+c}).compute(a,d);c=s.create(a.words.slice(b),4*c);a.sigBytes=4*b;return n.create({key:a,iv:c,salt:d})}},c=d.PasswordBasedCipher=a.extend({cfg:a.cfg.extend({kdf:p}),encrypt:function(b,c,d,l){l=this.cfg.extend(l);d=l.kdf.execute(d,
                            b.keySize,b.ivSize);l.iv=d.iv;b=a.encrypt.call(this,b,c,d.key,l);b.mixIn(d);return b},decrypt:function(b,c,d,l){l=this.cfg.extend(l);c=this._parse(c,l.format);d=l.kdf.execute(d,b.keySize,b.ivSize,c.salt);l.iv=d.iv;return a.decrypt.call(this,b,c,d.key,l)}})}();
(function(){for(var u=CryptoJS,p=u.lib.BlockCipher,d=u.algo,l=[],s=[],t=[],r=[],w=[],v=[],b=[],x=[],q=[],n=[],a=[],c=0;256>c;c++)a[c]=128>c?c<<1:c<<1^283;for(var e=0,j=0,c=0;256>c;c++){var k=j^j<<1^j<<2^j<<3^j<<4,k=k>>>8^k&255^99;l[e]=k;s[k]=e;var z=a[e],F=a[z],G=a[F],y=257*a[k]^16843008*k;t[e]=y<<24|y>>>8;r[e]=y<<16|y>>>16;w[e]=y<<8|y>>>24;v[e]=y;y=16843009*G^65537*F^257*z^16843008*e;b[k]=y<<24|y>>>8;x[k]=y<<16|y>>>16;q[k]=y<<8|y>>>24;n[k]=y;e?(e=z^a[a[a[G^z]]],j^=a[a[j]]):e=j=1}var H=[0,1,2,4,8,
    16,32,64,128,27,54],d=d.AES=p.extend({_doReset:function(){for(var a=this._key,c=a.words,d=a.sigBytes/4,a=4*((this._nRounds=d+6)+1),e=this._keySchedule=[],j=0;j<a;j++)if(j<d)e[j]=c[j];else{var k=e[j-1];j%d?6<d&&4==j%d&&(k=l[k>>>24]<<24|l[k>>>16&255]<<16|l[k>>>8&255]<<8|l[k&255]):(k=k<<8|k>>>24,k=l[k>>>24]<<24|l[k>>>16&255]<<16|l[k>>>8&255]<<8|l[k&255],k^=H[j/d|0]<<24);e[j]=e[j-d]^k}c=this._invKeySchedule=[];for(d=0;d<a;d++)j=a-d,k=d%4?e[j]:e[j-4],c[d]=4>d||4>=j?k:b[l[k>>>24]]^x[l[k>>>16&255]]^q[l[k>>>
        8&255]]^n[l[k&255]]},encryptBlock:function(a,b){this._doCryptBlock(a,b,this._keySchedule,t,r,w,v,l)},decryptBlock:function(a,c){var d=a[c+1];a[c+1]=a[c+3];a[c+3]=d;this._doCryptBlock(a,c,this._invKeySchedule,b,x,q,n,s);d=a[c+1];a[c+1]=a[c+3];a[c+3]=d},_doCryptBlock:function(a,b,c,d,e,j,l,f){for(var m=this._nRounds,g=a[b]^c[0],h=a[b+1]^c[1],k=a[b+2]^c[2],n=a[b+3]^c[3],p=4,r=1;r<m;r++)var q=d[g>>>24]^e[h>>>16&255]^j[k>>>8&255]^l[n&255]^c[p++],s=d[h>>>24]^e[k>>>16&255]^j[n>>>8&255]^l[g&255]^c[p++],t=
            d[k>>>24]^e[n>>>16&255]^j[g>>>8&255]^l[h&255]^c[p++],n=d[n>>>24]^e[g>>>16&255]^j[h>>>8&255]^l[k&255]^c[p++],g=q,h=s,k=t;q=(f[g>>>24]<<24|f[h>>>16&255]<<16|f[k>>>8&255]<<8|f[n&255])^c[p++];s=(f[h>>>24]<<24|f[k>>>16&255]<<16|f[n>>>8&255]<<8|f[g&255])^c[p++];t=(f[k>>>24]<<24|f[n>>>16&255]<<16|f[g>>>8&255]<<8|f[h&255])^c[p++];n=(f[n>>>24]<<24|f[g>>>16&255]<<16|f[h>>>8&255]<<8|f[k&255])^c[p++];a[b]=q;a[b+1]=s;a[b+2]=t;a[b+3]=n},keySize:8});u.AES=p._createHelper(d)})();

var Base64 = {
    encode : function (input) {
                 return CryptoJS.enc.Base64.stringify( CryptoJS.enc.Utf8.parse(input) );
             },
}

var Crypt = {
    cryptLevel: 0
        , postNumsXorPattern: 154 
        , nowXorPattern:212 
        , readXorPattern: 113
        , countXorPattern:45 
        , getZeroBytes: function() {
            if (this.zeroBytes == null) {
                this.zeroBytes = this.genIvBytes("");
            }
            return this.zeroBytes;
        }
    , setKey: function(level, key) {
        this.cryptLevel = Number(level);
        if (isNaN(this.cryptLevel) || (this.cryptLevel < 0 && 6 <= this.cryptLevel)) {
            this.cryptLevel = 0;
        }
        this.keyBytes = null;
        if (key) {
            this.keyBytes = CryptoJS.enc.Utf8.parse(""+key);
            this.keyBytes.sigBytes = 16;
        }
        if (this.keyBytes == null) {
            this.cryptLevel = 0;
        }
    }
    , removeKey: function (){
        this.cryptLevel =  0;
        this.keyBytes = null;
    }
    , encPostNums: function(nums, url) {
        if (this.cryptLevel == 0) {
            return nums.join(',');
        }
        var ivBytes = this.genIvBytes(this.reverseString(url));
        this.applyXorBytePattern(ivBytes.words, this.postNumsXorPattern);

        var list =  [];
        for (var i in nums) {
            list.push( this.enc(nums[i]+"", ivBytes));
        }

        return list.join(',');
    }
    , decPostNums: function(text, url) { 
        var list = text ? text.split(',') : [];
        if (this.cryptLevel == 0) {
            return list;
        }

        var outList = [];
        var ivBytes = this.genIvBytes(this.reverseString(url));
        this.applyXorBytePattern(ivBytes.words, this.postNumsXorPattern);
        for (var i in list) {
            var decoded = this.dec(list[i], ivBytes);
            outList.push(decoded);
        }
        return outList;

    }
    ,reverseString: function(str) {
        str = str+"";
        var result = "";
        for (var i= str.length-1; i >= 0; i--) {
            result += str[i];
        }
        return result;
    }

    , encUrl: function(url) { return this.cryptLevel < 3 ? this.escapeXML(url) : this.enc(this.reverseString(url+""), this.getZeroBytes()); }
    , decUrl: function(text) { return this.cryptLevel < 3 ? text : this.reverseString( ""+this.dec(text+"", this.getZeroBytes())); }
    , encTitle: function(title, url) { return this.cryptLevel < 2 ? this.escapeXML(title) : this.enc(title, this.genIvBytes(this.reverseString(""+url))); }
    , decTitle: function(text, url) { return this.cryptLevel < 2 ? text : this.dec(text, this.genIvBytes(this.reverseString(url))); }
    , encFolder: function(name, url) { return this.cryptLevel < 4 ? this.escapeXML(name) : this.enc(name, this.getZeroBytes());}
    , decFolder: function(text, url) { return this.cryptLevel < 4 ? text : this.dec(text, this.getZeroBytes());}
    , encNow: function(pos, url) { return this.cryptLevel < 5 ? pos : this.encNum(pos, url, this.nowXorPattern);}
    , decNow: function(text, url) { return this.cryptLevel < 5 ? text : this.decNum(text, url, this.nowXorPattern); }
    , encRead: function(read, url) { return this.cryptLevel < 5 ? read : this.encNum(read, url, this.readXorPattern); }
    , decRead: function(text, url) { return this.cryptLevel < 5 ? text : this.decNum(text, url, this.readXorPattern);  }
    , encCount: function(count, url) { return this.cryptLevel < 5 ? count : this.encNum(count, url, this.countXorPattern); }
    , decCount: function(text, url) { return this.cryptLevel < 5 ? text : this.decNum(text, url, this.countXorPattern);  } 

    , encNum : function(pos, url, pattern) {
        var ivBytes = this.genIvBytes(this.reverseString(url));
        this.applyXorBytePattern(ivBytes.words, pattern);
        return this.enc(new String(pos), ivBytes);
    }
    , decNum: function(pos, url, pattern) {
        var ivBytes = this.genIvBytes(this.reverseString(url));
        this.applyXorBytePattern(ivBytes.words, pattern);
        return this.dec(pos, ivBytes);
    }
    , genIvBytes: function(str) {
        try {
            var ivBytes = CryptoJS.enc.Utf8.parse(str);
            ivBytes.sigBytes = 16;
            return ivBytes;
        }catch(e) {
            v2c.println(e);
            v2c.println(str);
            return str;
        }
    }
    , applyXorBytePattern : function(words, xorPatternByte) {
        if (!words) {
            return ;
        }
        for (i = 0; i < 16; ++i) {
            word = words[i];
            words[i] = ((((word >> 24) & 0xFF) ^ xorPatternByte) << 24)
                |((((word >> 16) & 0xFF) ^ xorPatternByte) << 16)
                |((((word >> 8) & 0xFF) ^ xorPatternByte) << 8)
                |(((word & 0xFF) ^ xorPatternByte) << 0);
        }
    }
    , escapeXML: function(str) {
        str = str + '';
        return str.replace(/&/g, '&amp;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;')
            .replace(/"/g, '&quot;');
    }
    , enc: function(str, ivBytes) {
        if (this.keyBytes == null) return str;
        try {
            var encrypted = CryptoJS.AES.encrypt(CryptoJS.enc.Utf8.parse(str), this.keyBytes,
                    { iv: ivBytes});
        } catch(e) {
            v2c.println(e);
            return str;
        }

        return encrypted;
    }
    , dec: function(str, ivBytes) {
        if (str == null) return "";
        if (this.keyBytes == null) return str;

        try {
            var decrypted = CryptoJS.AES.decrypt(str, this.keyBytes,
                    { iv:ivBytes });
        }catch(e) {
            v2c.println(e);
            return str;
        }

        var byteArray = this.wordToByteArray(decrypted.words, decrypted.sigBytes);
        return this.utf8ByteArrayToString(byteArray);
    }
    , utf8ByteArrayToString : function(bytes) {
        var out = [], pos = 0, c = 0;
        while (pos < bytes.length) {
            var c1 = bytes[pos++];
            if (c1 < 128) {
                out[c++] = String.fromCharCode(c1);
            } else if (c1 > 191 && c1 < 224) {
                var c2 = bytes[pos++];
                out[c++] = String.fromCharCode((c1 & 31) << 6 | c2 & 63);
            } else {
                var c2 = bytes[pos++];
                var c3 = bytes[pos++];
                out[c++] = String.fromCharCode(
                        (c1 & 15) << 12 | (c2 & 63) << 6 | c3 & 63);
            }
        }
        return out.join('');
    }
    , wordToByteArray: function(wordArray, sigBytes) {
        var byteArray = [], word, i, j;
        var byteCount = 0;
        for (i = 0; i < wordArray.length; ++i) {
            word = wordArray[i];
            for (j = 3; j >= 0; --j) {
                byteArray.push((word >> 8 * j) & 0xFF);

                byteCount ++;
                if (byteCount >= sigBytes) break;
            }
            if (byteCount >= sigBytes) break;
        }
        return byteArray;
    }
};



try {
    main();
} catch(e) {
    var message = "■エラー " + e.lineNumber+ "行目: "+ e.toString(); 
    v2c.println(message);
    v2c.context.setStatusBarText(message);
}

function replaceRequestXML(xml){
	xml = (''+xml).replace(/url="(https?):\/\/([^\.]+)\.2ch\.net\//gi, 'url="$1://$2.5ch.net/');
	if(!REQ_5CH_HTTP)   xml = (''+xml).replace(/url="http:\/\/([^\.]+)\.5ch\.net\//gi,    'url="https://$1.5ch.net/');
	if(!REQ_5CH_HTTP)   xml = (''+xml).replace(/url="http:\/\/([^\.]+)\.bbspink\.com\//gi,'url="https://$1.bbspink.com/');
	if(REQ_JBBS_HTTPS)  xml = (''+xml).replace(/url="http:\/\/jbbs\.shitaraba\./gi,       'url="https://jbbs.shitaraba.');
	if(REQ_MACHI_HTTPS) xml = (''+xml).replace(/url="http:\/\/([^\.]+\.)?machi\.to\//gi,  'url="https://$1machi.to/');
	if(REQ_MACHI_NOREG){
		xml = (''+xml).replace(/url="(https?):\/\/[^\.]+\.machi\.to\//gi, 'url="$1://machi.to/');
	}else{
		xml = (''+xml).replace(/url="(https?:\/\/)(machi\.to\/)(bbs\/read\.cgi\/)?([^\/]+)/gi, function(re0, re1, re2, re3, re4) {
			return 'url="' + re1 + getSubHost(re4) + '.' + re2 + re3 + re4;
		});
	}
	return xml;
}

function replaceResponseXML(inputStream, conn, isGZIP){
	debug('isGZIP='+isGZIP);
	var xml = new java.lang.String();
	var encoding = conn.getContentEncoding();
	if(null == encoding){
		encoding = 'UTF-8';
	}

	if(!isGZIP){
		var inReader = new java.io.InputStreamReader(inputStream, encoding);
		var bufReader = new java.io.BufferedReader(inReader);
	
		// テキストを読み込む
		var line = '';
		while((line=bufReader.readLine()) != null){
			xml += line;
		}
	
		bufReader.close();
		inReader.close();
	}else{
		var decompressBaos = new java.io.ByteArrayOutputStream();
		var b = 0;
		while((b=inputStream.read()) != -1)
		{
			decompressBaos.write(b);
		}
		xml = decompressBaos.toString('utf-8');
	}

	if(!RES_5CH_NATIVE){
		xml = (''+xml).replace(/url="https?:\/\/([^\.]+)\.[25]ch\.net\//gi, 'url="http://$1.2ch.net/');
	}
	xml     = (''+xml).replace(/url="https?:\/\/([^\.]+)\.bbspink\.com\//gi,'url="http://$1.bbspink.com/');
	xml     = (''+xml).replace(/url="https:\/\/jbbs\.shitaraba\./gi,        'url="http://jbbs.shitaraba.');
	xml     = (''+xml).replace(/url="https:\/\/([^\.]+\.)?machi\.to\//gi,   'url="http://$1machi.to/');
	if(RES_MACHI_NOREG){
		xml = (''+xml).replace(/url="(https?):\/\/[^\.]+\.machi\.to\//gi, 'url="$1://machi.to/');
	}else{
		xml = (''+xml).replace(/url="(https?:\/\/)(machi\.to\/)(bbs\/read\.cgi\/)?([^\/]+)/gi, function(re0, re1, re2, re3, re4) {
			return 'url="' + re1 + getSubHost(re4) + '.' + re2 + re3 + re4;
		});
	}
  
	debug(xml);

	return xml;
}

function getSubHost(s){
	switch(s){
		case 'hokkaidou':
			return 'hokkaido';

		case 'touhoku':
			return 'tohoku';

		case 'kousinetu':
			return 'kousinetu';

		case 'kanto':
			return 'kanto';

		case 'tokyo':
			return 'tokyo';

		case 'tama':
			return 'kanto';

		case 'kana':
			return 'kanto';

		case 'toukai':
			return 'tokai';

		case 'kinki':
			return 'kinki';

		case 'osaka':
			return 'kinki';

		case 'cyugoku':
			return 'chugoku';

		case 'sikoku':
			return 'sikoku';

		case 'kyusyu':
			return 'kyusyu';

		case 'okinawa':
			return 'okinawa';

		case 'tawara':
			return 'www';
	}
}
