Light12306/RwTicketAssistantV2/app/contentscripts/framework.js

674 lines
18 KiB
JavaScript
Raw Normal View History

var $doc = $(document);
var sysConfig, currentProfile;
var seatOrder = ["", "", "", "", "9", "P", "M", "O", "6", "4", "3", "2", "1", "0", "*"];
var isDebug = (compressFunc + '').indexOf("false") != -1;
var tagMap = {
"动车或高铁或城铁": "[DGC].*",
"特快或直达": "[TZ].*",
"快车": "K.*",
"高铁": "G.*",
"动车": "D.*",
"城铁": "C.*",
"直达": "Z.*",
"特快": "T.*",
"任意车次": ".*"
};
var passengerTypeCode = {
"1": "成人",
"2": "儿童",
"3": "学生",
"4": "残疾军人、伤残人民警察"
};
var EVT_ONQUERYPAGE = "onQueryPage";
var EVT_ONLOGINPAGE = "onLoginPage";
var EVT_ONSUBMITPAGE = "onSubmitPage";
var EVT_ONMAINPAGE = "onMainPage";
var EVT_ONPASSENGERPAGE = "onPassengerPage";
var evtTag = new Date().getTime();
Date.prototype.format = function (format) {
/// <summary>格式化指定日期</summary>
/// <param name="format" type="String">格式化字符串</param>
format = format || "yyyy-MM-dd";
var o = {
"M+": this.getMonth() + 1, //month
"d+": this.getDate(), //day
"h+": this.getHours(), //hour
"m+": this.getMinutes(), //minute
"s+": this.getSeconds(), //second
"q+": Math.floor((this.getMonth() + 3) / 3), //quarter
"S": this.getMilliseconds() //millisecond
};
if (/(y+)/i.test(format)) {
format = format.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
}
for (var k in o) {
if (new RegExp("(" + k + ")").test(format)) {
format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ("00" + o[k]).substr(("" + o[k]).length));
}
}
return format;
};
Date.prototype.trimToDay = function () {
/// <summary>返回当前的日期和时间中的日期部分</summary>
return new Date(this.getFullYear(), this.getMonth(), this.getDate());
};
$.fn.extend({
nativeClick: function (timeout) {
/// <summary>触发原生的点击</summary>
var __ = this;
setTimeout(function () {
__.each(function () {
this.click && this.click();
});
}, timeout || 10);
return __;
},
nativeFocus: function () {
/// <summary>触发原生的获得焦点</summary>
this.each(function () {
this.focus && this.focus();
});
return this;
},
toBase64Data: function (callback) {
var internalcallback = function () {
$(this).unbind("load", internalcallback);
var canvas = document.createElement("canvas");
var height = this.naturalHeight;
var width = this.naturalWidth;
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext("2d");
ctx.drawImage(this, 0, 0, width, height);
var url = canvas.toDataURL();
delete canvas;
url = url.substr(url.indexOf(",") + 1);
callback && callback.call(this, url);
};
this.each(function () {
if (this.naturalHeight === 0) {
$(this).load(internalcallback);
} else {
internalcallback.call(this);
}
});
return this;
}
});
//#region 消息处理
var msg = (function () {
var control = function () {
var handlermap = {};
var __ = this;
this.on = function (name, callback) {
var queue = handlermap[name] = handlermap[name] || [];
queue.push(callback);
};
this.off = function (name, callback) {
var queue = handlermap[name] = handlermap[name] || [];
for (var i = queue.length - 1; i >= 0; i++) {
if (queue[i] === callback) {
queue.splice(i, 1);
}
}
};
chrome.runtime.onMessage.addListener(function (message, sender, response) {
if (message.tag && message.tag === evtTag)
return;
var action = message.action || "";
var queue = handlermap[action];
if (!queue) return;
$.each(queue, function () {
return this.call(message.detail, response, message, sender);
});
});
this.send = function (message, callback) {
message = $.extend(message, { tag: evtTag });
chrome.runtime.sendMessage(null, message, callback || function () { });
};
this.sendAction = function (action, detail, callback) {
__.send({ action: action, detail: detail }, callback);
};
};
return new control();
})();
//#endregion
//#region 辅助函数
var getTicketInfo = function (v) {
var data = {}, info = v.indexOf("#") === -1 ? v : /getSelected\(['"](.*?)['"]\)/i.exec(v)[1].split('#')[11],
match = info.match(/([A-Z\d])0*?([\*\d]{5})0*?(\d{4})/gi);
for (var j in match) {
var m = /([A-Z\d])0*?([\*\d]{5})0*?(\d{4})/i.exec(match[j]);
var sc = m[1];
var sp = m[2][0] == '*' ? null : parseInt(m[2], 10);
var st = parseInt(m[3], 10);
if (st < 3000) {
data[sc] = st;
data["_" + sc] = sp;
//一等软座 7 二等软座 8
if (sc == "7") {
data['M'] = st;
data["_M"] = sp;
}
else if (sc == "8") {
data['O'] = st;
data['_O'] = sp;
}
} else {
data['0'] = st - 3000;
data['_0'] = sp;
}
};
return data;
};
var log = (function () {
var fun = function () {
this.print = function () {
/// <summary>向控制台打印消息</summary>
if (!isDebug) return;
console.log.apply(console, $.makeArray(arguments));
};
return this;
};
return new fun();
})();
function compressFunc() {
return false;
}
(function injectCode() {
var s = document.createElement("script");
s.id = "s_" + evtTag;
s.textContent = "(" + ((function () {
document.addEventListener('$', function (e) { eval(e.detail); });
var x = document.getElementById("s_$");
document.head.removeChild(x);
}) + '').replace(/\$/g, evtTag) + ')();';
document.head.appendChild(s);
})();
function unsafeInvoke(callback) {
/// <summary>非沙箱模式下的回调</summary>
if (isDebug) {
var cb = document.createElement("script");
cb.type = "text/javascript";
cb.textContent = buildCallback(callback);
document.head.appendChild(cb);
} else {
document.dispatchEvent(new CustomEvent(evtTag, { detail: buildCallback(callback) }));
}
}
function buildCallback(callback) {
var content = "";
var name = "";
if (typeof (callback) == "function") name = callback.name;
if (!name) name = Math.random() + "";
content += "(" + buildObjectJavascriptCode(callback) + ")();" + (isDebug ? "\r\n//@ sourceURL=http://www.fishlee.net/soft/44/scripts/" + name + ".js" : "");
return content.replace(/\$_\$/g, evtTag);
}
function buildObjectJavascriptCode(obj) {
/// <summary>将指定的Javascript对象编译为脚本</summary>
if (obj === null) return "null";
if (obj === undefined) return 'undefined';
var t = typeof (obj);
if (t === "string") {
return "\"" + obj.replace(/(\r|\n|\\)/gi, function (a, b) {
switch (b) {
case "\r":
return "\\r";
case "\n":
return "\\n";
case "\\":
return "\\\\";
}
return b;
}) + "\"";
}
if (t !== "object") return obj + "";
var code = [];
for (var i in obj) {
var o = obj[i];
var objType = typeof (o);
if (objType === "object" || objType === "string") {
code.push(i + ":" + buildObjectJavascriptCode(o));
} else {
code.push(i + ":" + o);
}
}
return "{" + code.join(",") + "}";
}
//#endregion
//数据交换
var toRawPassenger = function (p) {
return {
passenger_name: p.name,
passenger_type: p.type,
passenger_id_type_code: p.idtype,
passenger_id_no: p.id,
passenger_first_letter: p.firstLetter,
mobile_no: "",
passenger_id_type_name: p.idtypeName,
passenger_type_name: p.typename
};
};
var evalCode = (function () {
var dataExchange;
document.body.addEventListener(evtTag, function (e) {
dataExchange = e && e.detail && e.detail.action == "dr" && e.detail.detail;
});
var fun = function () {
this.eval = function (code) {
if (jQuery.isFunction(code))
code = "(" + code + ")()";
document.dispatchEvent(new CustomEvent(evtTag, { detail: { action: "eval", detail: { code: code } } }));
return dataExchange;
};
};
return new fun();
})();
(function () {
$.fn.extend({
getTrimedText: function () {
return $.trim(this.text());
}
});
})();
//#region 系统配置更新函数和引导函数
function refreshSysConfig(callback) {
chrome.runtime.sendMessage({ action: "getSysConfig" }, function (message) {
sysConfig = message.detail;
$doc.trigger("sysConfigUpdated");
callback();
});
msg.on("sysConfigUpdate", function () {
sysConfig = this;
$doc.trigger("sysConfigUpdated");
});
}
$(function () {
var timer = null, timercheck = null, routed = false;
var ensureReady = function () {
chrome.runtime.sendMessage({ action: "isReady" }, function () {
if (timer)
clearInterval(timer);
if (timercheck)
clearInterval(timercheck);
if (!routed) {
routed = true;
$(document).trigger("routePage");
}
});
};
var notloadNotify = function () {
if (timer)
clearInterval(timer);
if (timercheck)
clearInterval(timercheck);
chrome.runtime.sendMessage({
action: "notLoadTip", detail: {
url: self.location + '',
host: self.location.host
}
});
};
log.print(evtTag);
refreshSysConfig(function () {
timer = setInterval(ensureReady, 100);
timercheck = setTimeout(notloadNotify, 15000);
ensureReady();
});
msg.send({ action: "enter12306" });
});
//#endregion
//#region 查询页公共函数
$(document).on("filterTrains", function (evt, data) {
if (!currentProfile) return;
//过滤车次列表
log.print("开始过滤车次");
if (currentProfile.hideNoTicket) {
//隐藏无票车次
var filtered = _.where(data.available, { available: 0 });
$.each(filtered, function () {
this.reason = 1;
data.filtered.push(this);
if (data.rowInfo)
data.rowInfo[this.id].$row.remove();
log.print("车次【" + this.code + "】因为无票被过滤。");
});
data.available = _.difference(data.available, filtered);
}
if (currentProfile.hideNotSameFrom) {
//隐藏发站不同车次
var filtered1 = _.filter(data.available, function (e) { return e.from.code != currentProfile.fromCode; });
$.each(filtered1, function () {
this.reason = 2;
data.filtered.push(this);
if (data.rowInfo)
data.rowInfo[this.id].$row.remove();
log.print("车次【" + this.code + "】因为发站不一致被过滤。");
});
data.available = _.difference(data.available, filtered1);
}
if (currentProfile.hideNotSameTo) {
//隐藏到站不同车次
var filtered2 = _.filter(data.available, function (e) { return e.to.code != currentProfile.toCode; });
$.each(filtered2, function () {
this.reason = 3;
data.filtered.push(this);
if (data.rowInfo)
data.rowInfo[this.id].$row.remove();
log.print("车次【" + this.code + "】因为发站不一致被过滤。");
});
data.available = _.difference(data.available, filtered2);
}
//按照时间过滤
(function () {
var r1 = _.filter(data.available, function (e) {
var t1 = parseInt(/^0*(\d+)\:/i.exec(e.from.time)[1], 10);
var t2 = parseInt(/^0*(\d+)\:/i.exec(e.to.time)[1], 10);
return t1 < currentProfile.timeRangeDepFrom || t1 > currentProfile.timeRangeDepTo || t2 < currentProfile.timeRangeArrFrom || t2 > currentProfile.timeRangeArrTo;
});
$.each(r1, function () {
this.reason = 4;
data.filtered.push(this);
if (data.rowInfo)
data.rowInfo[this.id].$row.remove();
log.print("车次【" + this.code + "】因为时间不在指定的区域中而被过滤。");
});
data.available = _.difference(data.available, r1);
})();
//如果开启了自动预定,那么不显示预定结果以外的车次
if (currentProfile.selectedTrain && currentProfile.selectedTrain.length) {
var rule = new RegExp("^(" + currentProfile.selectedTrain.join("|") + ")$", "i");
var filtered3 = _.filter(data.available, function (e) { return !rule.test(e.code); });
data.include = _.difference(data.available, filtered3);
if (currentProfile.hideNotInListTrain) {
$.each(filtered3, function () {
this.reason = 5;
data.filtered.push(this);
if (data.rowInfo)
data.rowInfo[this.id].$row.remove();
log.print("车次【" + this.code + "】不在自动预定列表中而被过滤。");
});
data.available = data.include;
}
}
log.print("车次过滤结束。");
});
$(document).on("processTrains", function (e, data) {
if (!currentProfile)
return;
var count = Math.max(currentProfile.partialSubmitEnabled ? 0 : currentProfile.passengers.length, 1);
var seat = null, train = null, entry = null;
if (data.original.length && !data.include.length && currentProfile.selectedTrain && currentProfile.selectedTrain.length && !currentProfile.byAuto) {
msg.sendAction("notify", {
title: "提示",
message: "貌似没有查到您要预定的任何车次,是否设置存在问题?",
iconUrl: "/icons/icon_128.png"
});
}
var trainRegCache = _.map(currentProfile.selectedTrain, function (s) { return new RegExp("^" + s + "$", "i"); });
if (!trainRegCache || !trainRegCache.length) {
trainRegCache = [/.*/];
}
//搜索车次
if (currentProfile.selectSeatFirst) {
//席别优先
seat = _.find(currentProfile.selectedSeatType, function (s) {
var _treg = _.find(trainRegCache, function (t) {
entry = _.find(data.available, function (item) {
return t.test(item.code) && item.tickets[s] >= count;
});
return typeof (entry) != 'undefined';
});
return typeof (_treg) != 'undefined';
});
} else {
//车次优先
_.find(trainRegCache, function (t) {
seat = _.find(currentProfile.selectedSeatType, function (s) {
entry = _.find(data.available, function (item) {
return t.test(item.code) && item.tickets[s] >= count;
});
return typeof (entry) != 'undefined';
});
return typeof (seat) != 'undefined';
});
}
//查找结果
if (seat) {
train = entry.code;
//乘客
var ticketCount = entry.tickets[seat];
var pcount = Math.min(ticketCount, currentProfile.passengers.length);
data.auto = {
train: train,
seat: seat,
passengers: _.first(currentProfile.passengers, pcount),
data: entry
};
} else {
data.auto = null;
}
data.allowAutoSubmit = false;
//判断是否需要继续刷新
if (data.auto !== null) {
//找到车次了
data.nextTime = null;
if (currentProfile.autoSubmitEnabled) {
chrome.runtime.sendMessage({ action: "checkAutoRefreshEnabled" }, function (info) {
data.allowAutoSubmit = info.enabled;
if (info.enabled)
$(document).trigger("performAutoSubmit", data.auto);
});
}
} else {
//规则:如果只有待售的,那么等到指定的时间
//如果没有且在整点附近的,那么等到整点
//否则就按常规时间
var now = new Date();
if (data.available.length && sysConfig.autoWaitToSell && currentProfile.autoWaitToSell && data.include.length && _.every(data.include, function (x) { return x.available === -1; })) {
var next = _.min(_.map(data.include, function (x) {
return x.selltime;
}));
data.nextTime = now.getDate() === next.getDate() && next > now ? (next - now) / 1000 + 15 : 5;
} else if (now.getMinutes() >= 59) {
data.nextTime = 61 - now.getSeconds();
} else {
data.nextTime = 5;
}
}
log.print("queryReport");
if ($("#autoSubmit:checked").length || (!currentProfile.autoSubmitEnabled && $("#auto_query:checked").length)) {
data.auto = null;
}
chrome.runtime.sendMessage({
action: "queryStatisticsReport",
detail: {
stat: _.omit(data, "rowInfo"),
auto: data.auto,
nextTime: data.nextTime
}
});
});
//查询失败
(function () {
$(document).bind("qe", function () {
if ($("#auto_query:checked").length) {
var a = $("#query_ticket").data("suspend", true)[0];
a && a.click();
}
chrome.runtime.sendMessage({
action: "queryStatisticsReport",
detail: {
failed: true,
nextTime: 5
}
});
});
})();
//#endregion
//#region 验证码识别
$(document).on("routePage", function () {
var failedCount = 0;
var tipShown = false;
var stopped = false;
$(document).on("randCodeLoaded", function (e, id) {
var nowMinutes = new Date().getMinutes();
if ((nowMinutes >= 59 && nowMinutes <= 5) || (nowMinutes >= 28 && nowMinutes <= 35))
return;
if (!sysConfig || !sysConfig.enableAutoCaptcha) return;
if (failedCount >= sysConfig.autoCaptchaFailedLimit) {
failedCount = 0;
tipShown = false;
//msg.sendAction("pageNotification", { title: "验证码识别失败", message: "验证码识别失败,请手动填写" });
return;
}
if (!tipShown) {
tipShown = true;
//msg.sendAction("pageNotification", { title: "正在自动识别验证码", message: "正在为您识别验证码。验证码识别不一定成功且受限网络速度,可能的话请尽量手动填写。点击验证码输入框取消自动识别。" });
}
if (stopped) {
stopped = false;
return;
}
var img = $("#" + id);
var type = /rand=([a-z]+)/.exec(img.attr("src")) && RegExp.$1;
img.toBase64Data(function (url) {
chrome.runtime.sendMessage({ action: "captcha", detail: { base64: url } }, function (info) {
if (stopped) {
stopped = false;
failedCount = 0;
return;
}
var code = info.code;
if (code && code.length == 4) {
img.parent().prev("input").val(code);
//msg.sendAction("pageNotification", { title: "验证码识别成功", message: "验证码识别成功,请看看是否正确再提交!" });
}
//if (!code || code.length !== 4) {
// failedCount++;
// img.nativeClick();
//} else {
// //判断是否起效
// $.post("/otn/passcodeNew/checkRandCodeAnsyn", {
// randCode: code,
// rand: type || 'sjrand'
// }, function (data) {
// if (!data.status || data.data === 'N') {
// //失败
// failedCount++;
// img.nativeClick();
// } else {
// failedCount = 0;
// tipShown = false;
// msg.sendAction("pageNotification", { title: "验证码识别成功", message: "验证码识别成功,请看看是否正确再提交!" });
// img.parent().prev("input").val(code).change().keyup();
// }
// });
//}
});
});
});
$(document).on("click", "#randCode, #randCode2", function () {
stopped = true;
});
$("#img_rand_code:visible, #img_rand_code2:visible").each(function () {
if (this.naturalHeight > 0)
$(document).trigger("randCodeLoaded", this.getAttribute("id"));
else {
$(this).load(function () {
$(this).unbind("load");
$(document).trigger("randCodeLoaded", this.getAttribute("id"));
});
}
});
});
//#endregion