// ==UserScript== // @name webvpn登录预约系统 // @namespace https://bbs.tampermonkey.net.cn/ // @version 0.1.0 // @description try to take over the world! // @author tfsn20 // @crontab 1 7 * * * // @grant GM_xmlhttpRequest // @grant GM_log // @grant GM_notification // @grant GM_getValue // ==/UserScript== /* ==UserConfig== 账号和密码: studentIDs: title: 账号 default: 多个使用英文逗号分割, 如332218391, 342912393 type: textarea IDPasswords: title: 密码 default: 和账号一一对应, 多个使用英文逗号分割, 如332218391, {gilight}_NSskdf password: true 时间和场地: time: title: 开始时间 default: 多个使用英文逗号分割, 如15, 16 type: textarea priorChoices: title: 优先场地 default: 多个使用英文逗号分割, 不想选择某个场地就不要填写哪个场地的序号, 比如想让按照6, 5, 4, 8, 9, 7的顺序选择, 但不想选1, 2, 3则填入6, 5, 4, 8, 9, 7 type: textarea less: title: 至少场地位数 default: 填入2表示场地的可用位数少于2则不预约 type: textarea 推送通知: AccessKey: title: AccessKey default: 在sct.icodef.com/push/key获取 password: true tags: title: 设备标签名称 default: 在sct.icodef.com/push/device获取, 如有多个使用英文逗号分割, 如体育馆预约, 核酸结果 type: textarea ==/UserConfig== */ return new Promise(async (resolve, reject) => { function g_(str) { return str.replace(/,\s*$/, '').replace(/,\s*/g, ',').split(',') } let studentIDs = g_(GM_getValue('账号和密码.studentIDs')); let IDPasswords = g_(GM_getValue('账号和密码.IDPasswords')); IDPasswords = IDPasswords.map(function (str) { return str.includes('{gilight}_') ? str : `{gilight}_${btoa(encodeURIComponent(str))}`; }); let time = GM_getValue('时间和场地.time').replace(/,\s*$/, '').replace(/,\s*/g, ',').split(',').map(Number); let priorChoices = GM_getValue('时间和场地.priorChoices').replace(/,\s*$/, '').replace(/,\s*/g, ',').split(',').map(Number); let less = GM_getValue('时间和场地.less') - 0; let access_key = GM_getValue('推送通知.AccessKey'); function buildQueryStringFromArray(tagsArray) { // 使用数组的reduce方法来构建查询字符串 return tagsArray.reduce((queryString, tag, index) => { // 根据索引判断当前标签是不是第一个,如果不是第一个,则前面加上"&tags=" const prefix = index === 0 ? '' : '&tags='; return `${queryString}${prefix}${tag}`; }, ''); } let tags = g_(GM_getValue('推送通知.tags')); tags = buildQueryStringFromArray(tags) console.log(studentIDs, IDPasswords, time, priorChoices, less) const tools = { sleep: t => new Promise(res => setTimeout(res, t)),// 箭头函数体只有一句,可以省略return net: { change: (str, withCookie) => { str = str.trim(); //去除文本首位\s let method = str.match(/(.*?)\s/)[1]; let url = str.match(/\s(.*?)\s/)[1]; str = str.replace(/.*?\n/, ''); //去除第一行 let data = /\n\s*\n\s*(.*)$/m.test(str) ? str.slice(str.match(/\n\s*\n\s*(.*)$/m).index).trim() : ''; //获取请求体,没有则返回'', 用slice截取防止请求体含有\n时出错 str = str.replace(new RegExp(data), '').trim(); //去除data str = str.replace(/^(\s*?)(\S.*)/gm, `$2`); //去除每行行首的\s str = str.replace(/\sContent-Length:.*/im, ''); //去除Content-Length所在行,不用/^\sContent-Length:./im, 这样做会多一个空白行避免一些问题 str = withCookie ? str : str.replace(/\s*cookie:.*/im, '') //去除cookie所在行,不用/^\s*cookie.*/im, 这样做会多一个空白行 let headers = {}, h = str.match(/(\S*?):\s*(\S.*)/mg); // GM_log(h) h.forEach(e => { let t = e.match(/(\S*?):\s*(.*)/); headers[t[1]] = t[2].replace(/\s*$/, '');//去除行尾\s,避免一下总没错吧 }); //去除浏览器默认携带的请求头 if (!/origin:/i.test(str)) headers.origin = '';//脚本猫默认有一个拓展origin,这里去掉 if (!/dnt/i.test(str)) headers.dnt = ''; if (!/referer/i.test(str)) headers.referer = ''; //if (!/accept/i.test(str)) headers.accept=''; if (!/user-agent/i.test(str)) headers['user-agent'] = ''; if (!/sec-ch-ua/i.test(str)) headers['sec-ch-ua'] = ''; if (!/sec-ch-ua-mobile/i.test(str)) headers['sec-ch-ua-mobile'] = ''; if (!/sec-ch-ua-platform/i.test(str)) headers['sec-ch-ua-platform'] = ''; if (!/sec-fetch-dest/i.test(str)) headers['sec-fetch-dest'] = ''; if (!/sec-fetch-mode/i.test(str)) headers['sec-fetch-mode'] = ''; if (!/sec-fetch-site/i.test(str)) headers['sec-fetch-site'] = ''; //if (!/accept-language/i.test(str)) headers['accept-language']=''; if (!/accept-encoding/i.test(str)) headers['accept-encoding'] = ''; return { url, method, data, headers } }, } }; tools.net.send = (str, onload = (xhr => xhr), anonymous = false, withCookie = true) => { let t = tools.net.change(str, withCookie); return new Promise((resolve, reject) => { t.anonymous = anonymous; t.onload = (xhr) => { resolve(onload(xhr)) }; t.onerror = _ => { GM_log('some errors:' + _) }; t.onreadystatechange = _ => { if (_.status == 0) resolve('联系服务器之前发生了错误') } GM_xmlhttpRequest(t) }) }; // 账号登录和预约系统认证 async function loginWebvpnAndReservation(studentID = '332211020770', IDPassword = '{gilight}_MjgyOTk5MjU1MEFiYy4u') { // 获取登录参数 let loginPara = await tools.net.send(` GET https://webvpn.zzuli.edu.cn/http/77726476706e69737468656265737421fbee52d23d2a7d5c77468ca88d1b203b/cas/login?service=https%3A%2F%2Fwebvpn.zzuli.edu.cn%2Flogin%3Fcas_login%3Dtrue HTTP/1.1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Accept-Encoding: gzip, deflate, br, zstd Accept-Language: zh-CN,zh;q=0.9 Connection: keep-alive DNT: 1 Host: webvpn.zzuli.edu.cn Referer: https://webvpn.zzuli.edu.cn/login Sec-Fetch-Dest: document Sec-Fetch-Mode: navigate Sec-Fetch-Site: same-origin Sec-Fetch-User: ?1 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0 sec-ch-ua: "Microsoft Edge";v="125", "Chromium";v="125", "Not.A/Brand";v="24" sec-ch-ua-mobile: ?0 sec-ch-ua-platform: "Windows" `) if (loginPara == '联系服务器之前发生了错误') { return '联系服务器之前发生了错误' } else if (loginPara.finalUrl == 'https://webvpn.zzuli.edu.cn/') { return '已经登录' } let lt = loginPara.response.match(/value="(LT-.*-jinghua)"/)[1], execution = loginPara.response.match(/name="execution" value="(.*)"/)[1]; console.log('成功获取登录参数'); // 登录重定向 let login302 = await tools.net.send(` POST https://webvpn.zzuli.edu.cn/http/77726476706e69737468656265737421fbee52d23d2a7d5c77468ca88d1b203b/cas/login?service=https%3A%2F%2Fwebvpn.zzuli.edu.cn%2Flogin%3Fcas_login%3Dtrue HTTP/1.1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Accept-Encoding: gzip, deflate, br, zstd Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6,ja;q=0.5,de-DE;q=0.4,de;q=0.3 Cache-Control: max-age=0 Connection: keep-alive Content-Length: 169 Content-Type: application/x-www-form-urlencoded DNT: 1 Host: webvpn.zzuli.edu.cn Origin: https://webvpn.zzuli.edu.cn Referer: https://webvpn.zzuli.edu.cn/http/77726476706e69737468656265737421fbee52d23d2a7d5c77468ca88d1b203b/cas/login?service=https%3A%2F%2Fwebvpn.zzuli.edu.cn%2Flogin%3Fcas_login%3Dtrue Sec-Fetch-Dest: document Sec-Fetch-Mode: navigate Sec-Fetch-Site: same-origin Sec-Fetch-User: ?1 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0 sec-ch-ua: "Microsoft Edge";v="125", "Chromium";v="125", "Not.A/Brand";v="24" sec-ch-ua-mobile: ?0 sec-ch-ua-platform: "Windows" username=${studentID}&password=${IDPassword}=&secret=&accountLogin=<=${lt}&execution=${execution}&_eventId=submit `) console.log('成功登录webvpn'); // SetSource await tools.net.send(` GET https://webvpn.zzuli.edu.cn/http/77726476706e69737468656265737421f3f05885692a72457201c7a99c406d3651/User/SetSource?vpn-12-o1-cgyy.zzuli.edu.cn&Source=2&_=${Date.now()} HTTP/1.1 Accept: */* Accept-Encoding: gzip, deflate, br, zstd Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6,ja;q=0.5,de-DE;q=0.4,de;q=0.3 Cache-Control: no-cache Connection: keep-alive DNT: 1 Host: webvpn.zzuli.edu.cn Pragma: no-cache Referer: https://webvpn.zzuli.edu.cn/http/77726476706e69737468656265737421f3f05885692a72457201c7a99c406d3651/Views/User/UserChoose.html?wrdrecordvisit=1714397297000 Sec-Fetch-Dest: empty Sec-Fetch-Mode: cors Sec-Fetch-Site: same-origin User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0 X-Requested-With: XMLHttpRequest sec-ch-ua: "Microsoft Edge";v="125", "Chromium";v="125", "Not.A/Brand";v="24" sec-ch-ua-mobile: ?0 sec-ch-ua-platform: "Windows" `) // 体育管重定向 let sport302 = await tools.net.send(` GET https://webvpn.zzuli.edu.cn/http/77726476706e69737468656265737421f3f05885692a72457201c7a99c406d3651/User/UserChoose?LoginType=1 HTTP/1.1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Accept-Encoding: gzip, deflate, br, zstd Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6,ja;q=0.5,de-DE;q=0.4,de;q=0.3 Cache-Control: no-cache Connection: keep-alive DNT: 1 Host: webvpn.zzuli.edu.cn Pragma: no-cache Referer: https://webvpn.zzuli.edu.cn/http/77726476706e69737468656265737421f3f05885692a72457201c7a99c406d3651/Views/User/UserChoose.html?wrdrecordvisit=1714376686000 Sec-Fetch-Dest: document Sec-Fetch-Mode: navigate Sec-Fetch-Site: same-origin Sec-Fetch-User: ?1 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0 sec-ch-ua: "Microsoft Edge";v="125", "Chromium";v="125", "Not.A/Brand";v="24" sec-ch-ua-mobile: ?0 sec-ch-ua-platform: "Windows" `); console.log('成功登录体育馆预约系统') return true } // 场地预约 function reserve(fieldName, beginTime, fieldNumber = 1) { let fieldNo = (fieldName - 1) * 4 + fieldNumber, endTime = beginTime + 1; fieldNo = fieldNo < 10 ? '0' + fieldNo : '' + fieldNo return new Promise((resolve, reject) => { resolve(tools.net.send(` GET https://webvpn.zzuli.edu.cn/http/77726476706e69737468656265737421f3f05885692a72457201c7a99c406d3651/Field/OrderField?vpn-12-o1-cgyy.zzuli.edu.cn&checkdata=[{"FieldNo":"YMQ0${fieldNo}","FieldTypeNo":"03","FieldName":"羽毛球0${fieldName}-${fieldNumber}","BeginTime":"${beginTime}:30","Endtime":"${endTime}:30","Price":"0.00"}]&dateadd=0&VenueNo=001 HTTP/1.1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Accept-Encoding: gzip, deflate, br, zstd Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6,ja;q=0.5,de-DE;q=0.4,de;q=0.3 Cache-Control: no-cache Connection: keep-alive DNT: 1 Host: webvpn.zzuli.edu.cn Pragma: no-cache Sec-Fetch-Dest: document Sec-Fetch-Mode: navigate Sec-Fetch-Site: none Sec-Fetch-User: ?1 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0 sec-ch-ua: "Microsoft Edge";v="125", "Chromium";v="125", "Not.A/Brand";v="24" sec-ch-ua-mobile: ?0 sec-ch-ua-platform: "Windows" `)) }) } // 场地支付 function pay(oid, studentID = '332211020770') { return new Promise((resolve, reject) => { resolve(tools.net.send(` GET https://webvpn.zzuli.edu.cn/http/77726476706e69737468656265737421f3f05885692a72457201c7a99c406d3651/Field/CardPay?vpn-12-o1-cgyy.zzuli.edu.cn&PayNo=02&Money=0&CardMoney=1&Count=0.00&MemberNo=&CardNo=${studentID}&BillType=100&Password=&IsCheckPassword=0&OID=${oid}&VenueNo=001&PayDiscount=100&IsUseMemberType=1&EWMNum=1&_=${Date.now()} HTTP/1.1 Accept: */* Accept-Encoding: gzip, deflate, br, zstd Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6,ja;q=0.5,de-DE;q=0.4,de;q=0.3 Cache-Control: no-cache Connection: keep-alive DNT: 1 Host: webvpn.zzuli.edu.cn Pragma: no-cache Referer: https://webvpn.zzuli.edu.cn/http/77726476706e69737468656265737421f3f05885692a72457201c7a99c406d3651/Views/Pay/PayField.html?OID=${oid}&VenueNo=001 Sec-Fetch-Dest: empty Sec-Fetch-Mode: cors Sec-Fetch-Site: same-origin User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0 X-Requested-With: XMLHttpRequest sec-ch-ua: "Microsoft Edge";v="125", "Chromium";v="125", "Not.A/Brand";v="24" sec-ch-ua-mobile: ?0 sec-ch-ua-platform: "Windows" `)) }) } // 登出账号 function logout() { return new Promise((resolve, reject) => { resolve(tools.net.send(` GET https://webvpn.zzuli.edu.cn/logout HTTP/1.1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Accept-Encoding: gzip, deflate, br, zstd Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6,ja;q=0.5,de-DE;q=0.4,de;q=0.3 Cache-Control: no-cache Connection: keep-alive DNT: 1 Host: webvpn.zzuli.edu.cn Pragma: no-cache Referer: https://webvpn.zzuli.edu.cn/ Sec-Fetch-Dest: document Sec-Fetch-Mode: navigate Sec-Fetch-Site: same-origin Sec-Fetch-User: ?1 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0 sec-ch-ua: "Microsoft Edge";v="125", "Chromium";v="125", "Not.A/Brand";v="24" sec-ch-ua-mobile: ?0 sec-ch-ua-platform: "Windows" `)) }) } // TimePeriod=0,1,2分为上午下午(11)晚上(18) function venueState(TimePeriod = 1) { return new Promise((resolve, reject) => { resolve(tools.net.send(` GET https://webvpn.zzuli.edu.cn/http/77726476706e69737468656265737421f3f05885692a72457201c7a99c406d3651/Field/GetVenueState?vpn-12-o1-cgyy.zzuli.edu.cn&dateadd=0&TimePeriod=${TimePeriod}&VenueNo=001&FieldTypeNo=03&_=${Date.now()} HTTP/1.1 Accept: */* Accept-Encoding: gzip, deflate, br, zstd Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6,ja;q=0.5,de-DE;q=0.4,de;q=0.3 Cache-Control: no-cache Connection: keep-alive DNT: 1 Host: webvpn.zzuli.edu.cn Pragma: no-cache Referer: https://webvpn.zzuli.edu.cn/http/77726476706e69737468656265737421f3f05885692a72457201c7a99c406d3651/Views/Field/FieldOrder.html?VenueNo=001&FieldTypeNo=03 Sec-Fetch-Dest: empty Sec-Fetch-Mode: cors Sec-Fetch-Site: same-origin User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0 X-Requested-With: XMLHttpRequest sec-ch-ua: "Microsoft Edge";v="125", "Chromium";v="125", "Not.A/Brand";v="24" sec-ch-ua-mobile: ?0 sec-ch-ua-platform: "Windows" `)) }) } // 获取时间段每个场地的可用位置个数 async function getAvailableAmount(beginTime, TimePeriod = 1, less = 3) { let data = JSON.parse(JSON.parse((await venueState(TimePeriod)).response).resultdata); // 过滤出所有 BeginTime 为 beginTime 的场地 const filteredData = data.filter(item => item.BeginTime === `${beginTime}:30`); // 将数据按照场地编号进行分组 const groupedData = filteredData.reduce((acc, item) => { const fieldNumber = item.FieldName.split('-')[0]; // 取FieldName的第一部分作为场地编号 if (!acc[fieldNumber]) { acc[fieldNumber] = []; } acc[fieldNumber].push(item); return acc; }, {}); // 计算每个组中FieldState为0的个数 const groupCounts = Object.values(groupedData).map(group => ({ group: group[0].FieldName.split('-')[0], // 取FieldName的第一部分作为组的标识 count: group.filter(item => item.FieldState === '0').length })); // 找出FieldState为0的个数最多的组 const maxCount = Math.max(...groupCounts.map(item => item.count)); const bestGroups = groupCounts.filter(item => item.count === maxCount); return maxCount > (less - 1) ? bestGroups : []; // 返回最佳组, 没有则返回空数组 } // 查询位置可用情况 async function getIsAvailable(beginTime, fieldFullName) { let data = JSON.parse(JSON.parse((await venueState(TimePeriod)).response).resultdata); // 过滤出所有 BeginTime 为 beginTime 的场地 const filteredData = data.filter(item => item.BeginTime === beginTime); // 查找并输出符合条件的场地的 FieldState const field = filteredData.find(item => item.BeginTime === beginTime && item.FieldName === fieldFullName ); // 如果找到场地,返回 FieldState,否则返回未找到的提示 return field ? field.FieldState : '未找到对应场地或时间'; } // 获取策略 没有时返回false,否则根据priorChoices返回最佳值 function getTactics(availableAmount, priorChoices = [6, 5, 4, 8, 9, 2, 3, 1, 7], less = 2) { for (let i = 4; i >= less; i--) { // 过滤出所有 BeginTime 为 beginTime 的场地 const filteredData = availableAmount.filter(item => item.count === i); if (filteredData.length == 0) { continue } else { return priorChoices.find(bItem => filteredData.some(aItem => aItem.group.match(/羽毛球0(.*)/)[1] == bItem)) } } return false } function notify(title = '体育馆预约结果', content, tags, access_key) { return new Promise((resolve, reject) => { resolve(tools.net.send(` GET https://sct.icodef.com/openapi/v1/message/send?access_key=${access_key}&title=${title}&content=${content}&tags=${tags} HTTP/1.1 Host: sct.icodef.com User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0 Accept: */* Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate, br Referer: https://sct.icodef.com/push/key Connection: keep-alive Cookie: access_token=ANyIf54O1Rpuw1lmWMrB2AsI1R6thn0k Sec-Fetch-Dest: empty Sec-Fetch-Mode: cors Sec-Fetch-Site: same-origin DNT: 1 Sec-GPC: 1 Pragma: no-cache Cache-Control: no-cache `)) }) } (async () => { let flag = ''; let studentFlag = 0; for (let i = 0, len = time.length; i < len; i++) { let beginTime = time[i]; if (await loginWebvpnAndReservation(studentIDs[studentFlag], IDPasswords[studentFlag]) == '联系服务器之前发生了错误') { flag += `${studentIDs[studentFlag]}账号联系服务器之前发生了错误`; break }; let tactics = getTactics(await getAvailableAmount(beginTime, beginTime > 17 ? 2 : 1, less), priorChoices, less); console.log(`第${i + 1}轮的预约场地为${tactics}`); if (!tactics) { continue }; for (let j = 1; j < 5; j++) { if (studentFlag + 1 > studentIDs.length) { if (flag == '') { flag = '未完成任何预约' } break } if (await loginWebvpnAndReservation(studentIDs[studentFlag], IDPasswords[studentFlag]) == '联系服务器之前发生了错误') { flag += `${studentIDs[studentFlag]}账号联系服务器之前发生了错误`; break }; let reservation = JSON.parse((await reserve(tactics, beginTime, j)).response); console.log(`预约信息: ${reservation.message}, 预约oid: ${reservation.resultdata}`); if (reservation.resultdata) { console.log(`预约结果: ${(await pay(reservation.resultdata, studentIDs[studentFlag])).response}`); console.log((await logout()).finalUrl.includes('cas/logout') ? '成功登出账户' : '账号登出有误'); if (studentFlag >= studentIDs.length) { if (flag == '') { flag = '未完成任何预约' } } else { flag += `${studentIDs[studentFlag]}账号预约了${beginTime}:30的${tactics}-${j}%0A`; } studentFlag++; } else { continue } } } if (flag == '') { flag = '似乎没有预约信息' } console.log(decodeURIComponent(flag)); await notify('体育馆预约结果', flag, tags, access_key); resolve(flag) })(); });