// V2C Sync2ch Script
//【登録場所】 全体
//【ラベル】 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 (全タブ既読化)
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";
function main() {
DEBUG = hasArg( ARG_DEBUG );
SAVE_LOG = hasArg( ARG_LOG );
USESSL = !hasArg ( ARG_NOSSL );
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(clientIdFile);
if (clientIdFile.exists()) {
try {
var str = v2c.readFile(clientIdFile);
clientId = java.lang.Integer.parseInt(str);
} 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);
if (SAVE_LOG) saveSentXML(xml);
debug(xml);
bw.write(new java.lang.String(xml));
bw.close();
// レスポンスXML受信
var inputStream = conn.getInputStream();
var encHeader = conn.getHeaderField("Content-Encoding");
if (encHeader && encHeader+"" == "gzip") {
inputStream = new java.util.zip.GZIPInputStream(inputStream);
}
//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 = "\n";
xml += "\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 += "
>>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<>>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>>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;dd||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