UNPKG

watch12306

Version:

检测12306网站,并在有余票时候通知

309 lines (300 loc) 11 kB
'use strict'; /* * Created by hzwangfei3 on 2016/12/5. */ const colors = require('./util/colorful'); const notify = require('./util/notify'); const Type = require('./util/typeOf'); const query = require('./req'); const columnify = require('columnify'); const CANT_BUY = "还不能买"; const defaultNumArr = ["swz_num","tz_num","zy_num","ze_num","gr_num","rw_num","yw_num","rz_num","yz_num","wz_num","qt_num"]; const headers = { check: '满足', station_train_code: '车次', from_station_name: '出发站', to_station_name: '到达站', start_time: '出发时', arrive_time: '到达时', lishi: '历时', swz_num: '商务座', tz_num: '特等座', zy_num: '一等座', ze_num: '二等座', gr_num: '高铁软卧', rw_num: '软卧', yw_num: '硬卧', rz_num: '软座', yz_num: '硬座', wz_num: '无座', qt_num: '其它', controlled_train_message: '备注' }; const headersConfig = (function(){ let config = {}; Object.keys(headers).forEach(col=>{ config[col] = { headingTransform: function(heading) { return colors.gray.bold(headers[heading]) + "\r\n" + colors.gray(repeatChar("-", headers[heading].length*2)); } } }); return config; })(); const dataTransform = data => { let mutLine = data.split("\n"); if(Type.isUndefinedOrNull(data)){ //空数据 return ""; }else if(mutLine.length<2 && data.match(/(-)|(无)|(列车停运)|(起售)/)){ //-,列车停运,无 return colors.gray(~data.indexOf("--") ? (" " + data) : data); }else if(data.match(/^[A-Za-z]\d+/)){ //车次 return colors.cyan(data); }else if(mutLine.length>=2){ let l0 = mutLine[0]; let l1 = mutLine[1]; let noData = !!l0.match(/(-)|(无)|(\*)/); l0 = colors[noData?"gray":"cyan"](l0); l1 = colors[(noData||l1.match(/(-)|(无)|(\*)/))?"gray":"yellow"](l1); //票数+票价 return `${l0}\r\n${l1}`; }else if(data.match(/\d+:\d+/)){ //时间 return colors.cyan(data); // return colors.blue(data.replace(/:/g,"点")); }else if(data.match(/\d+/)){ //只有票数 return colors.cyan(data); }else if(~data.indexOf(CANT_BUY)){ //不能买 return colors.gray(data); }else if(data.match(/(true)|(false)/g)){ let space = " "; return data==="true" ? colors.green.bold(space + "Y") : colors.gray(space + "N"); }else{ return data; } }; // 左边补零 function cvn(n){ return String(n).length < 2 ? ("0" + n) : n; } // 当前时间 function getCurT(){ let res,cur = new Date(); res = "[" + colors.gray([cvn(cur.getHours()),cvn(cur.getMinutes()),cvn(cur.getSeconds())].join(":")) + "]"; return res; } // 标识码 function genUid(opt){ let cur = new Date(); let h = cur.getHours(); let m =cur.getMinutes(); let s = cur.getSeconds(); h = h >= 10 ? h : ("0"+h); m = m >= 10 ? m : ("0"+m); s = s >= 10 ? s : ("0"+s); return ((opt.from+">"+opt.to) || "未命名") + " " + [h, m, s].join(":") + "-" + (cur.getMilliseconds() % 1000) ; } // 判断是否有余票 function checkSurplusTickets(item,checkPropsArr){ item = item || {}; checkPropsArr = Type.isArray(checkPropsArr) && checkPropsArr.length > 0 ? checkPropsArr : defaultNumArr; let hasSurplus = false; let iLen = checkPropsArr.length; for(let i=0;i<iLen;i++){ let num = item[checkPropsArr[i]]; let lines = num.split("\n"); let up = lines[0]; if(up.match(/\d+/)){//纯数字 if(parseInt(up)>0){//余票数大于零 hasSurplus = true; break; } } } return hasSurplus; } // 精简字段 function simplifyData(data,config){ let res = {count:0}; let includeType = (config && config.condition && config.condition.include) || []; res.list = data.map((item) => { let canWebBuy = !item.canWebBuy || item.canWebBuy.toUpperCase()!=="N"; let hasSurplus = false; let checkIt = checkSurplusTickets(item,includeType); if(checkIt){ hasSurplus = true; } res.count += canWebBuy && hasSurplus ? 1 : 0; return { check: (canWebBuy && checkIt), station_train_code: item.station_train_code, from_station_name: item.from_station_name, to_station_name: item.to_station_name, start_time: item.controlled_train_flag === '0' ? item.start_time : "--", arrive_time: item.controlled_train_flag === '0' ? item.arrive_time : "--", lishi: item.lishi, swz_num: item.swz_num, tz_num: item.tz_num, zy_num: item.zy_num, ze_num: item.ze_num, gr_num: item.gr_num, rw_num: item.rw_num, yw_num: item.yw_num, rz_num: item.rz_num, yz_num: item.yz_num, wz_num: item.wz_num, qt_num: item.qt_num, controlled_train_message: item.controlled_train_flag === '1' ? item.controlled_train_message : (!canWebBuy? item.note.replace(/<br\/>/g,"") || canWebBuy : "" ) }; }); res.hasTicket = res.count; res.from = res.list[0].from_station_name; res.to = res.list[0].to_station_name; return res; } // 重复产生n和char并组成字符串返回 function repeatChar(char,n){ let res = ""; char = Type.isChar(char) ? char : "*"; n = parseInt(n) || 0; n = n > 0 ? n : 0; if(n>0) while(n--) res += char; return res; } // 输出 function printOut(data,config,uid){ let chineseInfo = config.info||{}; let info = colors.gray("("+chineseInfo.from+"->"+chineseInfo.to+","+chineseInfo.date+","+chineseInfo.onlyShowHighSpeedRail+chineseInfo.purposeCode+")"); if(!data || !data.length){ console.log(`\r\n${getCurT()} ${colors.cyan(">>>")} 监视器 ${colors.magenta(uid)} 查询到数据 0 条 ☹☹☹ ${info}\r\n`); return; } // 转换结果 let res= simplifyData(data,config); // 弹窗通知 res.hasTicket && notify({ title: res.hasTicket?"有票啦!":"查票完毕", message: `${res.from} => ${res.to} ${res.hasTicket?"有票啦!":"无票可买!"}`, sound: res.hasTicket || false, type: res.hasTicket ? 2 : 0, wait: res.hasTicket, time: res.hasTicket ? 30000 : 3000, openUrl: res.hasTicket ? "https://kyfw.12306.cn/otn/lcxxcx/init" : 0 }); // 控制台:输出信息概况 console.log(`\r\n${getCurT()} ${colors.cyan(">>>")} 监视器 ${colors.magenta(uid)} 查询到数据 ${colors.cyan(data.length)} 条! ${info}`); console.log(`${getCurT()} ${colors.gray("其中有效数据")} ${colors.green.bold(res.count)} ${colors.gray("条,有效条件:"+config.info.include.join(','))}\r\n`); // 控制台:输出详情列表 console.log(columnify(res.list,{ columnSplitter: colors.gray('|'), preserveNewLines: true, dataTransform: dataTransform, widths: { controlled_train_message: { maxWidth: 150 } }, config: headersConfig }) + '\r\n'); } // 查询并输出 function doQuery(config,uid){ // 查询一条 let promise = query(config.from, config.to, config.date, config.condition); promise.then( (res) => { printOut(res,config,uid); }, (err) => { console.log(err); } ); } // today 2016-07-08 function getToday(){ let cur = new Date(); return [cur.getFullYear(), cur.getMonth()+1, cur.getDate()].join("-"); } // 检查日期格式 function checkDateString(dateStr,defaultVal){ defaultVal = Type.isString(defaultVal) && defaultVal.match(/(\d{4})-(\d{1,2})-(\d{1,2})/) ? defaultVal : getToday(); if(Type.isString(dateStr)){ let arr = dateStr.replace(/\//g,"-").match(/(\d{4})-(\d{1,2})-(\d{1,2})/); if(arr.length===4){ return [arr[1],cvn(arr[2]),cvn(arr[3])].join("-"); }else{ return defaultVal; } }else{ return defaultVal; } } // 检测器类 class Watcher{ /** * 构造器 * @param option */ constructor(option) { option = option || {}; this.option = {}; this.option.from = option.from || "HZH"; this.option.to = option.to || "ZIW"; this.option.date = checkDateString(option.date); this.option.pollinterval = Type.isRealNumber(option.pollinterval) ? option.pollinterval : (3*60*1000);//3min this.option.condition = Type.isObject(option.condition) ? option.condition : {}; // this.option.condition.showPrice = 1; this.option.info = option.info; this.uid = genUid(this.option); this.timer = null; } /** * 重置配置参数 * @param option */ reset(option) { option = Type.isObject(option) ? option : {}; this.option.from = option.from || this.option.from; this.option.to = option.to || this.option.to; this.option.date = checkDateString(option.date,this.option.date); this.option.pollinterval = Type.isRealNumber(option.pollinterval) ? option.pollinterval : this.option.pollinterval; this.option.condition = Type.isObject(option.condition) ? option.condition : this.option.condition; this.option.info = Type.isObject(option.info) ? option.info : this.option.info; } /** * 启动监视器 * @param isRestart */ start(isRestart) { let self = this; self.stop(); let chineseInfo = self.option.info||{}; let info = colors.gray("("+chineseInfo.from+"->"+chineseInfo.to+","+chineseInfo.date+","+chineseInfo.onlyShowHighSpeedRail+chineseInfo.purposeCode+")"); console.log(`\r\n${getCurT()} ${!isRestart?"开启":"重启"}监视器 ${colors.magenta(self.uid)} ${info}`); doQuery(self.option,self.uid);// do at once self.timer = setInterval(()=>{ doQuery(self.option,self.uid); },self.option.pollinterval) } /** * 重启监视器 * @param option */ restart(option) { option && this.reset(option); // console.log(`${colors.magenta(this.uid)} is restart!`); this.start(1); } /** * 停止监视器 */ stop() { this.timer && clearInterval(this.timer); this.timer = null; } } module.exports = Watcher;