您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
【使用前先看介绍/有问题可反馈】深大抢馆 (Grab Venue in SZU):一键抢深圳大学粤海校区体育场馆。
// ==UserScript== // @name Grab Venue in SZU // @namespace http://tampermonkey.net/ // @version 1.0.5 // @description 【使用前先看介绍/有问题可反馈】深大抢馆 (Grab Venue in SZU):一键抢深圳大学粤海校区体育场馆。 // @author cc // @match http://ehall.szu.edu.cn/publicapp/sys/tycgyyxt/index.do* // @require http://greasyfork.icu/scripts/422854-bubble-message.js // @grant GM_setValue // @grant GM_getValue // ==/UserScript== (function () { const config = { DEBUG: false, // 是否开启调试,不要动这个选项 neededTimeSlot: '08:00-22:00', // 所需时间段 allTimeSlots: ['08:00-12:00', '14:00-22:00'], // 所有时间段 CQBM: '001', // 场区 ID (可能) CGBM: '002', // 场馆 ID (默认羽毛球馆) XMDM: '005', // 项目 ID (默认羽毛球) CYRS: 4, // 参与人数 (默认 4 人) startClock: '12:30', // 开始时间 timeLimit: 60, // 最大请求持续时间,单位为秒 } const bm = new BubbleMessage(); // define ['HH:00'] timePoint // define ['H1:00-H2:00'] timeSlot // define ['H1:00-H2:00' && H1 + 1 == H2] hourTimeSlot // define [H] hour function inform(msg) { var Xmsg = `%c${msg}`; console.warn(Xmsg, 'background-color: yellow; font-weight: bold;'); } function range(start, stop) { var r = []; for (let i = start; i <= stop; ++i) r.push(i); return r; } function timeFormat(milliseconds) { var unit = parseInt(milliseconds / 1000); var second = unit % 60; unit = parseInt(unit / 60); var minute = unit % 60; unit = parseInt(unit / 60); var hour = unit % 24; var ans = `${second} sec`; if (minute > 0) ans = `${minute} min ${ans}`; if (hour > 0) ans = `${hour} hour ${ans}`; return ans; } // 'YYYY-MM-DD' function getYYRQ() { now = new Date(Date.now() + 86400 * 1000); var month = `${now.getMonth() + 1}`.padStart(2, '0'); var date = `${now.getDate()}`.padStart(2, '0'); return `${now.getFullYear()}-${month}-${date}`; } // array[timeSlot] -> array[hour] function timeSlotsToNumbers(timeSlots, withoutEnd) { var numbers = []; for (let timeSlot of timeSlots) { var [start, end] = timeSlot.split('-').map(t => parseInt(t.match(/\d+/)[0])); numbers = numbers.concat(range(start, (withoutEnd ? end - 1 : end))); } numbers.sort((a, b) => a - b); return numbers; } // 'H1:00' -> 'H2:00' [H1 + 1 == H2] function nextTimePoint(hourTimePoint) { return `${parseInt(hourTimePoint.match(/\d+/)[0]) + 1}`.padStart(2, '0') + ':00'; } // 'H1:00-H2:00' -> 'H1:00-H3:00' [H2 + 1 == H3] function getHourTimeSlot(timeSlot) { var START = timeSlot.match(/\d+:00/)[0]; var END = nextTimePoint(START); return `${START}-${END}`; } // 'H1:00-H2:00' -> 'H3:00-H2:00' [H1 < H3 and H3:00-(H3+1):00 in allTimeSlots or H3 == H2] function nextTimeSlot(timeSlot) { var flag = false; while (!flag) { var [startTimePoint, endTimePoint] = timeSlot.match(/\d+:00/g); startTimePoint = nextTimePoint(startTimePoint); timeSlot = `${startTimePoint}-${endTimePoint}`; if (startTimePoint == endTimePoint) break; var hourTimeSlot = getHourTimeSlot(timeSlot); for (let ts of config.allTimeSlots) { if (isInTimeSlot(ts, hourTimeSlot)) { flag = true; break; } } } return timeSlot; } function isInTimeSlot(neededTimeSlot, timeSlot) { var tsRange = timeSlot.match(/\d+:00/g).map(s => parseInt(s.substr(0, 2))); let neededTsRange = neededTimeSlot.match(/\d+:00/g).map(s => parseInt(s.substr(0, 2))); if (tsRange[0] < neededTsRange[0] || neededTsRange[1] < tsRange[1]) return false; return true; } function TLE() { var tomorrowStartTime = new Date(`${getYYRQ()} ${config.startClock}`); var startTime = new Date(tomorrowStartTime.getTime() - 86400 * 1000); var tleTime = new Date(startTime.getTime() + config.timeLimit * 1000); var diffMillis = parseInt(Date.now() - tleTime.getTime()) / 1000; return diffMillis > (config.timeLimit * 1000); } function finishBooking(timeSlot) { var [start, end] = timeSlot.match(/\d+:00/g).map(e => parseInt(e.match(/\d+/)[0])); return start >= end; } function requestVenue(waiting, finished) { console.warn('in function requestVenue...'); if (waiting.length > 0) { let WID = waiting[0]; $.ajax({ url: 'http://ehall.szu.edu.cn/publicapp/sys/tycgyyxt/sportVenue/getOrderNum.do', method: 'POST', data: { wid: WID } }).then(res => { var DHID = res.DHID; if (config.DEBUG) { console.log(res); console.log(res.DHID); } var YYRQ = getYYRQ(); var KYYSJD = getHourTimeSlot(config.neededTimeSlot); var [START, END] = KYYSJD.split('-'); var [YYKS, YYJS] = [`${YYRQ} ${START}`, `${YYRQ} ${END}`]; $.ajax({ url: 'http://ehall.szu.edu.cn/publicapp/sys/tycgyyxt/sportVenue/insertVenueBookingInfo.do', method: 'POST', data: { DHID: DHID, YYRGH: localStorage.ampUserId, XQWID: config.XQWID, CQBM: config.CQBM, CGDM: config.CGBM, CDWID: WID, XMDM: config.XMDM, YYRQ: YYRQ, KYYSJD: KYYSJD, YYKS: YYKS, YYJS: YYJS, YYLX: 'YY_TT', BCXZRS: 6, CYRS: config.CYRS, QTYYR: [] } }).then(res => { if (config.DEBUG) { console.log(res); } if (res.success) { // 请求成功 config.__neededTimeSlot__ = nextTimeSlot(config.__neededTimeSlot__); if (!finishBooking(config.__neededTimeSlot__)) { // 存在其他时间段需要请求 requestVenue(waiting, finished); } else { // 所有时间段请求完毕 var message = 'All tasks completed.'; bm.message({ type: 'success', message: message, duration: parseInt((1000 * message.length / 14)), }); inform(message); switchAction(); } } else { // 请求失败,舍弃当前场馆,尝试下一场馆 finished.push(waiting[0]); waiting = waiting.slice(1); if (!TLE()) { requestVenue(waiting, finished); } else { var message = 'Tasks in all time periods were not completed, but the time limit was exceeded.'; bm.message({ type: 'warning', message: message, duration: parseInt((1000 * message.length / 14)), }); inform(message); switchAction(); } } }); }); } } function requestWIDs() { console.warn('in function requestWIDs...'); var [START, END] = config.neededTimeSlot.match(/\d{2}:00/g); $.ajax({ url: 'http://ehall.szu.edu.cn/publicapp/sys/tycgyyxt/sportVenue/getCdxx.do', method: 'POST', data: { CGBM: config.CGBM, XMDM: config.XMDM, TYPE: 'YY_TT', START: START, END: END, YYRQ: getYYRQ(), YYTYPE: '1.0' } }).then(res => { var WIDs = res.map(e => e.id); if (config.DEBUG) { console.log(res); console.log(WIDs); } if (WIDs.length == 0) { if (!TLE()) { setTimeout(requestWIDs, 250); } else { var message = 'No venue can be reserved, time limit exceeded.'; bm.message({ type: 'warning', message: message, duration: parseInt((1000 * message.length / 14)), }); inform(message); switchAction(); } } else { requestVenue(WIDs, []); } }); } function requestSjd() { console.warn('in function requestSjd...'); document.getElementById('grab-venue-in-szu-button').innerHTML = '正在进行抢馆'; $.ajax({ url: 'http://ehall.szu.edu.cn/publicapp/sys/tycgyyxt/sportVenue/getSjdByCg.do', method: 'POST', data: { CGBM: config.CGBM, XMDM: config.XMDM, TYPE: 'YY_TT', YYRQ: getYYRQ(), YYTYPE: '1.0' }, }).then(res => { var sjd = res.kyysjd.map(e => e.kyysjd); var timeslots = sjd.filter(e => isInTimeSlot(config.neededTimeSlot, e)); if (config.DEBUG) { console.log(res); console.log(sjd); console.log(timeslots); } if (timeslots.length == 0) { // 可用时间段为空 if (!TLE()) { setTimeout(requestSjd, 250); } else { var message = 'No available time period, time out of limit.'; bm.message({ type: 'warning', message: message, duration: parseInt((1000 * message.length / 14)), }); inform(message); switchAction(); } } else { // 存在可用时间段 requestWIDs(timeslots); } }); } function init() { console.warn('in function init...'); insertStyle(); insertButton(); insertSelector(); updateData(); $.ajax({ url: 'http://ehall.szu.edu.cn/publicapp/sys/tycgyyxt/sportVenue/getSchoolZoneDic.do', method: 'POST', async: true, }).then(res => { config.XQWID = res.datas.code.rows[0].id; }); } function standBy() { console.warn('in function standBy...'); var now = new Date(); var startHourMinute = config.startClock.split(':').map(e => parseInt(e)); var startClock = { hour: startHourMinute[0], minute: startHourMinute[1], second: 0, }; var currentClock = { hour: now.getHours(), minute: now.getMinutes(), second: now.getSeconds(), } if (config.DEBUG || (currentClock.hour < startClock.hour || (currentClock.hour == startClock.hour && currentClock.minute < startClock.minute))) { var diffMillis = ((startClock.hour - currentClock.hour) * 3600 + (startClock.minute - currentClock.minute) * 60 + (startClock.second - currentClock.second)) * 1000; config.__neededTimeSlot__ = config.neededTimeSlot; switchAction(); if (config.DEBUG) diffMillis = 7 * 1000; var intervalId = setInterval(() => { diffMillis -= 1000; document.getElementById('grab-venue-in-szu-button').innerText = `正在等待倒计时 ${timeFormat(diffMillis)}`; }, 1000); setTimeout(() => { clearInterval(intervalId); requestSjd(); }, diffMillis); var message = `Tasks will be executed in ${timeFormat(diffMillis)}.`; bm.message({ type: 'info', message: message, duration: parseInt((1000 * message.length / 14)), }); inform(message); } else { var message = 'The time limit is exceeded and the program is no longer executed.'; bm.message({ type: 'warning', message: message, duration: parseInt((1000 * message.length / 14)), }); inform(message); } return false; } function updateData(obj) { // 存储和修改使用 neededTimeSlot ,常量使用 __neededTimeSlot__ var data = GM_getValue('data'); if (!obj) { // 初始化调用 if (!data) { // 未初始化,写入更新 var start = document.getElementById('grab-venue-in-szu-select-start').value; var end = document.getElementById('grab-venue-in-szu-select-end').value; var neededTimeSlot = `${start}-${end}`; GM_setValue('data', { neededTimeSlot: neededTimeSlot }); config.neededTimeSlot = neededTimeSlot; config.__neededTimeSlot__ = neededTimeSlot; } else { // 已初始化,读取更新 data = GM_getValue('data'); Object.assign(config, data); config.__neededTimeSlot__ = config.neededTimeSlot; updateSelector(); } } else { // 更新调用 data = GM_getValue('data'); Object.assign(data, obj); GM_setValue('data', data); Object.assign(config, data); config.__neededTimeSlot__ = config.neededTimeSlot; } } function updateSelector() { var startSelect = document.getElementById('grab-venue-in-szu-select-start'); var endSelect = document.getElementById('grab-venue-in-szu-select-end'); var [start, end] = config.neededTimeSlot.split('-'); startSelect.value = start; endSelect.value = end; } function switchAction() { if (config.waiting) { config.waiting = false; var button = document.getElementById('grab-venue-in-szu-button'); button.innerHTML = '点击开启抢馆'; button.removeAttribute('disabled'); button.style.cursor = ''; var startSelect = document.getElementById('grab-venue-in-szu-select-start'); startSelect.removeAttribute('disabled'); startSelect.style.cursor = ''; var endSelect = document.getElementById('grab-venue-in-szu-select-end'); endSelect.removeAttribute('disabled'); endSelect.style.cursor = ''; } else { config.waiting = true; var button = document.getElementById('grab-venue-in-szu-button'); button.setAttribute('disabled', 'disabled'); button.style.cursor = 'wait'; var startSelect = document.getElementById('grab-venue-in-szu-select-start'); startSelect.setAttribute('disabled', 'disabled'); startSelect.style.cursor = 'not-allowed'; var endSelect = document.getElementById('grab-venue-in-szu-select-end'); endSelect.setAttribute('disabled', 'disabled'); endSelect.style.cursor = 'not-allowed'; } } function insertButton() { var button = document.createElement('button'); button.setAttribute('id', 'grab-venue-in-szu-button'); button.innerHTML = '点击开启抢馆'; var title = document.querySelector('.bh-headerBar-title'); title.parentElement.insertBefore(button, title); button.onclick = standBy; } function insertSelector() { function rangeToOption(n) { n = n.toString().padStart(2, '0') + ':00'; var option = document.createElement('option'); option.name = n; option.value = n; option.innerHTML = n; return option; } function getOptions(limit) { var r = timeSlotsToNumbers(config.allTimeSlots, !limit); if (limit) { limit = parseInt(limit.match(/\d+/)[0]); var earliest = 0; while (r[earliest] <= limit) earliest++; r = r.slice(earliest); } return r.map(rangeToOption); } var container = document.createElement('div'); container.setAttribute('id', 'grab-venue-in-szu-container'); var startText = document.createElement('span'); startText.innerHTML = '目标时间段'; container.appendChild(startText); var startSelect = document.createElement('select'); startSelect.setAttribute('id', 'grab-venue-in-szu-select-start'); var startOptions = getOptions(); startOptions.forEach(option => startSelect.appendChild(option)); startSelect.addEventListener('change', function(e) { var endSelect = document.getElementById('grab-venue-in-szu-select-end'); [...endSelect.children].forEach(option => option.remove()); var endOptions = getOptions(e.target.value); endOptions.forEach(option => endSelect.appendChild(option)); updateData({ neededTimeSlot: `${e.target.value}-${endSelect.value}` }); }); container.appendChild(startSelect); var toText = document.createElement('span'); toText.innerHTML = '至'; container.appendChild(toText); var endSelect = document.createElement('select'); endSelect.setAttribute('id', 'grab-venue-in-szu-select-end'); var endOptions = getOptions(startSelect.firstElementChild.value); endOptions.forEach(option => endSelect.appendChild(option)); endSelect.addEventListener('change', function(e) { var startSelect = document.getElementById('grab-venue-in-szu-select-start'); updateData({ neededTimeSlot: `${startSelect.value}-${e.target.value}` }); }); container.appendChild(endSelect); var button = document.getElementById('grab-venue-in-szu-button'); button.parentElement.insertBefore(container, button.nextElementSibling); } function insertStyle() { var style = document.createElement('style'); style.id = 'grab-venue-in-szu-style'; style.innerHTML = ` #grab-venue-in-szu-button { font-weight: bold; color: black; background-color: #ffffff; margin: 5px 15px 0; border-radius: 3px; border: 0; } #grab-venue-in-szu-container { display: inline-flex; justify-content: space-between; font-weight: bold; width: 240px; } #grab-venue-in-szu-container > span { color: white; cursor: default; } #grab-venue-in-szu-container > select { border-radius: 3px; cursor: pointer; } `; document.body.appendChild(style); } function check() { if (document.querySelector('.bh-headerBar-title')) { init(); } else { setTimeout(check, 250); } } check(); })();