// ==UserScript==
// @name 深圳大学体育场馆自动抢票 (iOS兼容)
// @namespace http://tampermonkey.net/
// @version 1.0.6
// @description 深圳大学体育场馆自动预约脚本 - 支持iPad/iPhone
// @author zskfree
// @match https://ehall.szu.edu.cn/qljfwapp/sys/lwSzuCgyy/*
// @icon 🎾
// @grant none
// @run-at document-end
// @license MIT
// @downloadURL none
// ==/UserScript==
(function () {
'use strict';
// 检测移动设备
const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
const isIOS = /iPhone|iPad|iPod/i.test(navigator.userAgent);
// 移动端适配的存储方案(不依赖GM_setValue)
const Storage = {
set: function (key, value) {
try {
localStorage.setItem('szu_sports_' + key, JSON.stringify(value));
} catch (e) {
console.warn('存储失败:', e);
}
},
get: function (key, defaultValue) {
try {
const item = localStorage.getItem('szu_sports_' + key);
return item ? JSON.parse(item) : defaultValue;
} catch (e) {
return defaultValue;
}
}
};
// 运动项目映射
const SPORT_CODES = {
"羽毛球": "001",
"排球": "003",
"网球": "004",
"篮球": "005",
"游泳": "009",
"乒乓球": "013",
"桌球": "016"
};
// 校区映射
const CAMPUS_CODES = {
"粤海": "1",
"丽湖": "2"
};
// 时间段选项
const TIME_SLOTS = [
"08:00-09:00", "09:00-10:00", "10:00-11:00", "11:00-12:00",
"12:00-13:00", "13:00-14:00", "14:00-15:00", "15:00-16:00",
"16:00-17:00", "17:00-18:00", "18:00-19:00", "19:00-20:00",
"20:00-21:00", "21:00-22:00"
];
// 场馆代码映射
const VENUE_CODES = {
"至畅": "104",
"至快": "111"
};
// 默认配置
const DEFAULT_CONFIG = {
USER_INFO: {
YYRGH: "2300123999",
YYRXM: "张三"
},
TARGET_DATE: getTomorrowDate(),
SPORT: "羽毛球",
CAMPUS: "丽湖",
PREFERRED_VENUE: "至畅", // 新增:优先场馆选择
PREFERRED_TIMES: ["20:00-21:00", "21:00-22:00"],
RETRY_INTERVAL: 1,
MAX_RETRY_TIMES: 200,
REQUEST_TIMEOUT: 10,
YYLX: "1.0"
};
// 获取明天日期
function getTomorrowDate() {
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
return tomorrow.toISOString().split('T')[0];
}
// 修改保存和加载配置函数
function saveConfig(config) {
Storage.set('bookingConfig', config);
}
function loadConfig() {
try {
const saved = Storage.get('bookingConfig', null);
return saved ? { ...DEFAULT_CONFIG, ...saved } : DEFAULT_CONFIG;
} catch (e) {
return DEFAULT_CONFIG;
}
}
function savePanelState(isVisible) {
Storage.set('panelVisible', isVisible);
}
function loadPanelState() {
return Storage.get('panelVisible', true);
}
// 全局变量
let CONFIG = loadConfig();
let isRunning = false;
let retryCount = 0;
let startTime = null;
let successfulBookings = [];
let controlPanel = null;
let floatingButton = null;
let isPanelVisible = loadPanelState();
// 获取动态最大预约数量
function getMaxBookings() {
const selectedTimeSlots = CONFIG.PREFERRED_TIMES.length;
return Math.min(selectedTimeSlots, 2); // 最多2个,但不超过选择的时间段数量
}
// 创建浮动按钮 - 替换原有函数
function createFloatingButton() {
const button = document.createElement('div');
button.id = 'floating-toggle-btn';
const mobileSize = isMobile ? '70px' : '60px';
const mobileFontSize = isMobile ? '28px' : '24px';
button.style.cssText = `
position: fixed;
top: ${isMobile ? '10px' : '20px'};
right: ${isMobile ? '10px' : '20px'};
width: ${mobileSize};
height: ${mobileSize};
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
z-index: 10001;
box-shadow: 0 4px 15px rgba(0,0,0,0.3);
transition: all 0.3s ease;
border: 3px solid rgba(255,255,255,0.2);
font-size: ${mobileFontSize};
user-select: none;
touch-action: manipulation;
`;
button.innerHTML = '🎾';
button.title = '显示/隐藏抢票面板';
// 移动端触摸优化
if (isMobile) {
button.addEventListener('touchstart', (e) => {
e.preventDefault();
button.style.transform = 'scale(1.1)';
});
button.addEventListener('touchend', (e) => {
e.preventDefault();
button.style.transform = 'scale(1)';
togglePanel();
});
} else {
// 桌面端鼠标事件
button.addEventListener('mouseenter', () => {
button.style.transform = 'scale(1.1)';
button.style.boxShadow = '0 6px 20px rgba(0,0,0,0.4)';
});
button.addEventListener('mouseleave', () => {
button.style.transform = 'scale(1)';
button.style.boxShadow = '0 4px 15px rgba(0,0,0,0.3)';
});
button.addEventListener('click', () => {
togglePanel();
});
}
document.body.appendChild(button);
return button;
}
// 创建控制面板 - 替换原有函数
function createControlPanel() {
const panel = document.createElement('div');
panel.id = 'auto-booking-panel';
// 移动端优化的样式
const mobileStyles = isMobile ? `
width: calc(100vw - 20px);
max-width: 400px;
top: 10px;
right: 10px;
left: 50%;
transform: translateX(-50%);
font-size: 16px;
max-height: calc(100vh - 20px);
touch-action: manipulation;
` : `
width: 400px;
top: 20px;
right: 90px;
max-height: 90vh;
`;
panel.style.cssText = `
position: fixed;
${mobileStyles}
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 15px;
padding: 20px;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
z-index: 10000;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
color: white;
border: 2px solid rgba(255,255,255,0.2);
overflow-y: auto;
transition: all 0.3s ease;
-webkit-overflow-scrolling: touch;
transform: translateX(0);
`;
// 移动端优化的输入框样式
const inputBaseStyle = `
width: 100%;
padding: ${isMobile ? '12px' : '6px'};
border: none;
border-radius: 4px;
background: rgba(255,255,255,0.9);
color: #333;
font-size: ${isMobile ? '16px' : '12px'};
box-sizing: border-box;
`;
// 移动端优化的按钮样式
const buttonBaseStyle = `
width: 100%;
padding: ${isMobile ? '15px' : '12px'};
border: none;
border-radius: 8px;
cursor: pointer;
font-size: ${isMobile ? '18px' : '16px'};
font-weight: bold;
transition: all 0.3s;
text-shadow: 1px 1px 2px rgba(0,0,0,0.3);
touch-action: manipulation;
`;
panel.innerHTML = `
🎾 自动抢票助手 v1.0.6
👤 ${CONFIG.USER_INFO.YYRXM} (${CONFIG.USER_INFO.YYRGH})
📅 ${CONFIG.TARGET_DATE} |
🏟️ ${CONFIG.SPORT} |
🏫 ${CONFIG.CAMPUS}
🏟️ 优先场馆: ${CONFIG.PREFERRED_VENUE || '至畅'}
⏰ ${CONFIG.PREFERRED_TIMES.join(', ')}
⚙️ 间隔:${CONFIG.RETRY_INTERVAL}s |
重试:${CONFIG.MAX_RETRY_TIMES} |
超时:${CONFIG.REQUEST_TIMEOUT}s
🎯 进度: 0/${getMaxBookings()} 个时段
${isMobile ? '📱 触摸优化版本' : '⚡ 快捷键: Ctrl+Shift+S 开始/停止 | Ctrl+Shift+H 显示/隐藏面板'}
`;
document.body.appendChild(panel);
// 根据保存的状态设置面板可见性
if (!isPanelVisible) {
panel.style.display = 'none';
}
bindEvents();
return panel;
}
// 切换面板显示/隐藏
function togglePanel() {
isPanelVisible = !isPanelVisible;
savePanelState(isPanelVisible);
if (controlPanel) {
if (isPanelVisible) {
controlPanel.style.display = 'block';
// 添加入场动画
controlPanel.style.transform = 'translateX(100%)';
controlPanel.style.opacity = '0';
setTimeout(() => {
controlPanel.style.transition = 'all 0.3s ease';
controlPanel.style.transform = 'translateX(0)';
controlPanel.style.opacity = '1';
}, 10);
} else {
// 添加退场动画
controlPanel.style.transition = 'all 0.3s ease';
controlPanel.style.transform = 'translateX(100%)';
controlPanel.style.opacity = '0';
setTimeout(() => {
controlPanel.style.display = 'none';
}, 300);
}
}
// 更新浮动按钮样式
if (floatingButton) {
if (isPanelVisible) {
floatingButton.style.background = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';
floatingButton.innerHTML = '🎾';
floatingButton.title = '隐藏抢票面板';
} else {
floatingButton.style.background = 'linear-gradient(135deg, #ff6b6b 0%, #ee5a52 100%)';
floatingButton.innerHTML = '📱';
floatingButton.title = '显示抢票面板';
}
}
}
// 绑定事件
function bindEvents() {
// 面板关闭按钮
document.getElementById('close-panel').addEventListener('click', () => {
togglePanel();
});
// 配置显示/隐藏
document.getElementById('toggle-config').addEventListener('click', () => {
const configArea = document.getElementById('config-area');
if (configArea.style.display === 'none') {
configArea.style.display = 'block';
document.getElementById('toggle-config').textContent = '⚙️ 隐藏配置';
} else {
configArea.style.display = 'none';
document.getElementById('toggle-config').textContent = '⚙️ 显示配置';
}
});
// 运动项目变化时显示/隐藏场馆选择
document.getElementById('sport-type').addEventListener('change', () => {
const sportType = document.getElementById('sport-type').value;
const venueSelection = document.getElementById('venue-selection');
const venueDisplay = document.getElementById('venue-display');
if (sportType === '羽毛球') {
venueSelection.style.display = 'block';
venueDisplay.style.display = 'block';
} else {
venueSelection.style.display = 'none';
venueDisplay.style.display = 'none';
}
});
// 保存配置
document.getElementById('save-config').addEventListener('click', () => {
updateConfigFromUI();
updateDisplayConfig();
addLog('✅ 配置已保存', 'success');
});
// 开始/停止按钮
document.getElementById('start-btn').addEventListener('click', () => {
if (isRunning) {
stopBooking();
} else {
updateConfigFromUI();
if (validateConfig()) {
startBooking();
}
}
});
// 快捷键 - 修改这部分
if (!isMobile) { // 只在非移动端添加快捷键
document.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.shiftKey) {
if (e.key === 'S') {
e.preventDefault();
if (isRunning) {
stopBooking();
} else {
updateConfigFromUI();
if (validateConfig()) {
startBooking();
}
}
} else if (e.key === 'H') {
e.preventDefault();
togglePanel();
} else if (e.key === 'C') {
e.preventDefault();
if (isPanelVisible) {
document.getElementById('toggle-config').click();
}
}
}
});
}
}
// 从UI更新配置
function updateConfigFromUI() {
// 获取选中的时间段
const selectedTimes = Array.from(document.querySelectorAll('#time-slots-container input[type="checkbox"]:checked'))
.map(cb => cb.value);
CONFIG = {
USER_INFO: {
YYRGH: document.getElementById('user-id').value.trim(),
YYRXM: document.getElementById('user-name').value.trim()
},
TARGET_DATE: document.getElementById('target-date').value,
SPORT: document.getElementById('sport-type').value,
CAMPUS: document.getElementById('campus').value,
PREFERRED_VENUE: document.getElementById('preferred-venue')?.value || '至畅', // 新增场馆选择
PREFERRED_TIMES: selectedTimes,
RETRY_INTERVAL: parseInt(document.getElementById('retry-interval').value),
MAX_RETRY_TIMES: parseInt(document.getElementById('max-retry').value),
REQUEST_TIMEOUT: parseInt(document.getElementById('request-timeout').value),
YYLX: "1.0"
};
saveConfig(CONFIG);
// 更新进度显示
updateProgress();
}
// 更新显示配置
function updateDisplayConfig() {
document.getElementById('display-user').textContent = `${CONFIG.USER_INFO.YYRXM} (${CONFIG.USER_INFO.YYRGH})`;
document.getElementById('display-date').textContent = CONFIG.TARGET_DATE;
document.getElementById('display-sport').textContent = CONFIG.SPORT;
document.getElementById('display-campus').textContent = CONFIG.CAMPUS;
// 更新场馆显示
const venueDisplayElement = document.getElementById('display-venue');
if (venueDisplayElement) {
venueDisplayElement.textContent = CONFIG.PREFERRED_VENUE || '至畅';
}
document.getElementById('display-times').textContent = CONFIG.PREFERRED_TIMES.join(', ');
document.getElementById('display-interval').textContent = CONFIG.RETRY_INTERVAL;
document.getElementById('display-retry').textContent = CONFIG.MAX_RETRY_TIMES;
document.getElementById('display-timeout').textContent = CONFIG.REQUEST_TIMEOUT;
}
// 验证配置
function validateConfig() {
if (!CONFIG.USER_INFO.YYRGH || !CONFIG.USER_INFO.YYRXM) {
addLog('❌ 请填写完整的用户信息', 'error');
return false;
}
if (CONFIG.PREFERRED_TIMES.length === 0) {
addLog('❌ 请至少选择一个时间段', 'error');
return false;
}
if (!CONFIG.TARGET_DATE) {
addLog('❌ 请选择预约日期', 'error');
return false;
}
// 新增:验证日期不能是过去
const targetDate = new Date(CONFIG.TARGET_DATE);
const today = new Date();
today.setHours(0, 0, 0, 0);
if (targetDate < today) {
addLog('❌ 预约日期不能是过去的日期', 'error');
return false;
}
// 新增:验证学号格式
if (!/^\d{8,12}$/.test(CONFIG.USER_INFO.YYRGH)) {
addLog('⚠️ 学号格式可能不正确,请检查', 'warning');
}
return true;
}
// 添加状态日志
function addLog(message, type = 'info') {
const statusArea = document.getElementById('status-area');
if (!statusArea) return;
const colors = {
info: '#e3f2fd',
success: '#c8e6c9',
warning: '#fff3e0',
error: '#ffcdd2'
};
const timestamp = new Date().toLocaleTimeString();
const logEntry = document.createElement('div');
logEntry.style.cssText = `
color: ${colors[type]};
margin-bottom: 3px;
border-left: 3px solid ${colors[type]};
padding-left: 8px;
`;
logEntry.innerHTML = `[${timestamp}] ${message}`;
statusArea.appendChild(logEntry);
statusArea.scrollTop = statusArea.scrollHeight;
// 保持最多50条日志
while (statusArea.children.length > 50) {
statusArea.removeChild(statusArea.firstChild);
}
}
// 更新预约进度
function updateProgress() {
const currentMaxBookings = getMaxBookings();
const progressElement = document.getElementById('booking-progress');
if (progressElement) {
progressElement.textContent = `${successfulBookings.length}/${currentMaxBookings} 个时段`;
}
}
// 获取可用时段
async function getAvailableSlots() {
try {
const allAvailable = [];
const sportCode = SPORT_CODES[CONFIG.SPORT];
const campusCode = CAMPUS_CODES[CONFIG.CAMPUS];
for (const timeSlot of CONFIG.PREFERRED_TIMES) {
const [startTime, endTime] = timeSlot.split("-");
const payload = new URLSearchParams({
XMDM: sportCode,
YYRQ: CONFIG.TARGET_DATE,
YYLX: CONFIG.YYLX,
KSSJ: startTime,
JSSJ: endTime,
XQDM: campusCode
});
const response = await fetch(
"https://ehall.szu.edu.cn/qljfwapp/sys/lwSzuCgyy/modules/sportVenue/getOpeningRoom.do",
{
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'application/json, text/javascript, */*; q=0.01'
},
body: payload
}
);
if (!response.ok) {
addLog(`❌ 请求失败: HTTP ${response.status}`, 'error');
continue;
}
const data = await response.json();
if (data.code !== "0") {
addLog(`❌ 查询时段 ${timeSlot} 失败: ${data.msg || '未知错误'}`, 'error');
continue;
}
if (data.datas && data.datas.getOpeningRoom) {
const rooms = data.datas.getOpeningRoom.rows || [];
let availableCount = 0;
for (const room of rooms) {
if (!room.disabled && room.text === "可预约") {
const venueName = room.CDMC || '';
// 根据场馆选择过滤
if (CONFIG.SPORT === "羽毛球" && CONFIG.PREFERRED_VENUE !== "全部") {
if (CONFIG.PREFERRED_VENUE === "至畅" && !venueName.includes("至畅")) {
continue; // 跳过非至畅场馆
}
if (CONFIG.PREFERRED_VENUE === "至快" && !venueName.includes("至快")) {
continue; // 跳过非至快场馆
}
}
let venuePriority = 2;
let courtPriority = 0; // 场地优先级,数字越小优先级越高
// 场馆优先级判断
if (venueName.includes("至畅")) {
venuePriority = 0; // 至畅最优先
// 丽湖校区至畅羽毛球场优先级设置
if (CONFIG.CAMPUS === "丽湖" && CONFIG.SPORT === "羽毛球") {
// 匹配"5号场"或"五号场"
if (venueName.includes("5号场") || venueName.includes("五号场")) {
courtPriority = -2; // 5号场地最优先
}
// 匹配"10号场"或"十号场"
else if (venueName.includes("10号场") || venueName.includes("十号场")) {
courtPriority = -1; // 10号场地次优先
}
// 匹配"1号场"或"一号场"
else if (venueName.match(/[^0-9]1号场|^1号场|一号场/)) {
courtPriority = 2; // 1号场地最低优先级
}
// 匹配"6号场"或"六号场"
else if (venueName.includes("6号场") || venueName.includes("六号场")) {
courtPriority = 2; // 6号场地最低优先级
}
// 其他至畅场地为默认优先级 0
}
} else if (venueName.includes("至快")) {
venuePriority = 1; // 至快次之
}
const slotInfo = {
name: `${timeSlot} - ${venueName}`,
wid: room.WID,
timeSlot: timeSlot,
startTime: startTime,
endTime: endTime,
venueName: venueName,
venueCode: room.CGBM || '',
priority: CONFIG.PREFERRED_TIMES.indexOf(timeSlot),
venuePriority: venuePriority,
courtPriority: courtPriority // 场地优先级
};
allAvailable.push(slotInfo);
availableCount++;
}
}
// 只在找到可预约场地时显示简化信息
if (availableCount > 0) {
addLog(`✅ ${timeSlot} 找到 ${availableCount} 个可预约场地`, 'success');
}
}
}
// 排序逻辑:优先级数字越小越优先
allAvailable.sort((a, b) => {
// 首先按场地优先级排序(数字越小优先级越高)
if (a.courtPriority !== b.courtPriority) {
return a.courtPriority - b.courtPriority;
}
// 其次按场馆优先级排序
if (a.venuePriority !== b.venuePriority) {
return a.venuePriority - b.venuePriority;
}
// 最后按时间优先级排序
return a.priority - b.priority;
});
// 🔍 简化调试信息显示
if (allAvailable.length > 0) {
// 只在羽毛球且有特殊优先级场地时显示详细信息
if (CONFIG.CAMPUS === "丽湖" && CONFIG.SPORT === "羽毛球") {
const hasSpecialCourts = allAvailable.some(slot =>
slot.courtPriority === -2 || slot.courtPriority === -1
);
if (hasSpecialCourts) {
const topSlot = allAvailable[0];
let priorityText = "";
if (topSlot.courtPriority === -2) {
priorityText = " (🏆 5号场优先)";
} else if (topSlot.courtPriority === -1) {
priorityText = " (⭐ 10号场)";
}
addLog(`🎯 优选场地: ${topSlot.venueName}${priorityText}`, 'info');
}
}
}
return allAvailable;
} catch (error) {
addLog(`🔥 获取时段失败: ${error.message}`, 'error');
return [];
}
}
// 预约场地
async function bookSlot(wid, slotName) {
try {
const timeSlot = CONFIG.PREFERRED_TIMES.find(time => slotName.includes(time));
if (!timeSlot) {
addLog(`❌ 无法从 ${slotName} 中提取时间信息`, 'error');
return false;
}
// 使用新的场馆代码映射
let venueCode = "104"; // 默认值
for (const [venueName, code] of Object.entries(VENUE_CODES)) {
if (slotName.includes(venueName)) {
venueCode = code;
break;
}
}
const [startTime, endTime] = timeSlot.split("-");
const sportCode = SPORT_CODES[CONFIG.SPORT];
const campusCode = CAMPUS_CODES[CONFIG.CAMPUS];
const payload = new URLSearchParams({
DHID: "",
YYRGH: CONFIG.USER_INFO.YYRGH,
CYRS: "",
YYRXM: CONFIG.USER_INFO.YYRXM,
CGDM: venueCode,
CDWID: wid,
XMDM: sportCode,
XQWID: campusCode,
KYYSJD: timeSlot,
YYRQ: CONFIG.TARGET_DATE,
YYLX: CONFIG.YYLX,
YYKS: `${CONFIG.TARGET_DATE} ${startTime}`,
YYJS: `${CONFIG.TARGET_DATE} ${endTime}`,
PC_OR_PHONE: "pc"
});
// 移除"正在预约"的重复提示,因为上面已经显示了
const response = await fetch(
"https://ehall.szu.edu.cn/qljfwapp/sys/lwSzuCgyy/sportVenue/insertVenueBookingInfo.do",
{
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'application/json, text/javascript, */*; q=0.01'
},
body: payload
}
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const result = await response.json();
if (result.code === "0" && result.msg === "成功") {
const dhid = result.data?.DHID || "Unknown";
addLog(`🎉 预约成功!场地:${slotName}`, 'success');
addLog(`📋 预约单号:${dhid}`, 'success');
successfulBookings.push({
timeSlot: timeSlot,
venueName: slotName,
dhid: dhid,
slotName: slotName
});
updateProgress();
return true;
} else {
const errorMsg = result.msg || "未知错误";
addLog(`❌ 预约失败:${errorMsg}`, 'error');
if (errorMsg.includes("只能预订2次") || errorMsg.includes("超过限制")) {
addLog(`🎊 已达到预约上限!`, 'success');
return 'limit_reached';
}
return false;
}
} catch (error) {
addLog(`💥 预约异常: ${error.message}`, 'error');
return false;
}
}
// 主抢票循环
async function startBooking() {
if (isRunning) return;
isRunning = true;
retryCount = 0;
startTime = new Date();
const currentMaxBookings = getMaxBookings(); // 获取当前最大预约数量
const startBtn = document.getElementById('start-btn');
if (startBtn) {
startBtn.textContent = '⏹️ 停止抢票';
startBtn.style.background = 'linear-gradient(45deg, #f44336, #d32f2f)';
}
addLog(`🚀 开始自动抢票!`, 'success');
addLog(`📊 ${CONFIG.SPORT} | ${CONFIG.CAMPUS} | ${CONFIG.TARGET_DATE} | 目标: ${currentMaxBookings} 个时段`, 'info');
// 添加场馆选择提示
if (CONFIG.SPORT === "羽毛球") {
if (CONFIG.PREFERRED_VENUE === "全部") {
addLog(`🏟️ 场馆策略: 全部场馆 (至畅 > 至快)`, 'info');
} else {
addLog(`🏟️ 场馆策略: 仅${CONFIG.PREFERRED_VENUE}体育馆`, 'info');
}
// 只在丽湖至畅时显示优先级提示
if (CONFIG.CAMPUS === "丽湖" && (CONFIG.PREFERRED_VENUE === "至畅" || CONFIG.PREFERRED_VENUE === "全部")) {
addLog(`🎾 至畅场地优先级: 5号 > 10号 > 其他 > 1号/6号`, 'info');
}
}
try {
while (isRunning && retryCount < CONFIG.MAX_RETRY_TIMES) {
if (successfulBookings.length >= currentMaxBookings) {
addLog(`🎊 恭喜!已成功预约 ${currentMaxBookings} 个时间段!`, 'success');
break;
}
retryCount++;
// 简化查询进度显示
if (retryCount === 1 || retryCount % 10 === 0 || retryCount <= 5) {
addLog(`🔍 第 ${retryCount} 次查询 (${successfulBookings.length}/${currentMaxBookings})`);
}
const availableSlots = await getAvailableSlots();
if (availableSlots.length > 0) {
// 简化找到场地的提示
if (availableSlots.length <= 5) {
addLog(`🎉 找到 ${availableSlots.length} 个可预约时段`, 'success');
} else {
addLog(`🎉 找到 ${availableSlots.length} 个可预约时段 (显示前5个)`, 'success');
}
const bookedTimeSlots = successfulBookings.map(booking => booking.timeSlot);
const remainingSlots = availableSlots.filter(slot =>
!bookedTimeSlots.includes(slot.timeSlot)
);
if (remainingSlots.length > 0) {
const timeSlotGroups = {};
remainingSlots.forEach(slot => {
if (!timeSlotGroups[slot.timeSlot]) {
timeSlotGroups[slot.timeSlot] = [];
}
timeSlotGroups[slot.timeSlot].push(slot);
});
for (const timeSlot of CONFIG.PREFERRED_TIMES) {
if (successfulBookings.length >= currentMaxBookings) break;
if (bookedTimeSlots.includes(timeSlot)) continue;
if (timeSlotGroups[timeSlot]) {
const slotsInTime = timeSlotGroups[timeSlot];
// 重新排序以确保优先级正确
slotsInTime.sort((a, b) => {
if (a.courtPriority !== b.courtPriority) {
return a.courtPriority - b.courtPriority;
}
return a.venuePriority - b.venuePriority;
});
const firstSlot = slotsInTime[0];
// 简化选择场地信息显示
let priorityText = "";
if (CONFIG.CAMPUS === "丽湖" && CONFIG.SPORT === "羽毛球" && firstSlot.venueName.includes("至畅")) {
if (firstSlot.courtPriority === -2) {
priorityText = " 🏆";
} else if (firstSlot.courtPriority === -1) {
priorityText = " ⭐";
} else if (firstSlot.courtPriority === 2) {
priorityText = " 🔻";
}
}
addLog(`🎯 预约: ${firstSlot.venueName}${priorityText}`, 'info');
const result = await bookSlot(firstSlot.wid, firstSlot.name);
if (result === true) {
addLog(`✨ ${timeSlot} 预约成功!`, 'success');
if (successfulBookings.length < currentMaxBookings) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
} else if (result === 'limit_reached') {
break;
} else {
await new Promise(resolve => setTimeout(resolve, 500));
}
}
}
}
} else {
// 简化无可用场地的提示
if (retryCount <= 3 || retryCount % 20 === 0) {
addLog(`🔍 暂无可预约场地`, 'warning');
}
}
if (successfulBookings.length < currentMaxBookings && isRunning && retryCount < CONFIG.MAX_RETRY_TIMES) {
// 只在前几次或间隔显示等待信息
if (retryCount <= 3 || retryCount % 30 === 0) {
addLog(`⏳ 等待 ${CONFIG.RETRY_INTERVAL} 秒后重试...`);
}
await new Promise(resolve => setTimeout(resolve, CONFIG.RETRY_INTERVAL * 1000));
}
}
} catch (error) {
addLog(`💥 程序异常: ${error.message}`, 'error');
} finally {
stopBooking();
}
}
// 停止抢票
function stopBooking() {
if (!isRunning) return; // 防止重复调用
isRunning = false;
const currentMaxBookings = getMaxBookings();
const startBtn = document.getElementById('start-btn');
if (startBtn) {
startBtn.textContent = '🚀 开始抢票';
startBtn.style.background = 'linear-gradient(45deg, #ff6b6b, #ee5a52)';
}
if (successfulBookings.length > 0) {
addLog(`🎉 抢票结束!成功预约 ${successfulBookings.length}/${currentMaxBookings} 个时段`, 'success');
successfulBookings.forEach((booking, index) => {
addLog(`${index + 1}. ${booking.slotName} (${booking.dhid})`, 'success');
});
} else {
addLog(`😢 很遗憾,没有成功预约到任何时段`, 'warning');
}
const elapsed = startTime ? Math.round((new Date() - startTime) / 1000) : 0;
addLog(`📊 运行时间: ${elapsed}秒, 查询次数: ${retryCount}`, 'info');
}
// 初始化
function init() {
if (!window.location.href.includes('ehall.szu.edu.cn/qljfwapp/sys/lwSzuCgyy')) {
return;
}
// 创建浮动按钮
floatingButton = createFloatingButton();
// 创建控制面板
controlPanel = createControlPanel();
updateDisplayConfig();
addLog(`🎮 自动抢票助手已就绪!(${isMobile ? '移动端' : '桌面端'})`, 'success');
if (isMobile) {
addLog(`📱 移动端优化版本,触摸操作优化`, 'info');
} else {
addLog(`⌨️ 快捷键: Ctrl+Shift+S 开始/停止 | Ctrl+Shift+H 显示/隐藏`, 'info');
}
addLog(`📝 已加载配置,可随时修改`, 'info');
}
})();