您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
川大自动抢课脚本 - 带有简单显示ui
当前为
// ==UserScript== // @name 四川大学自动抢课简单脚本 // @namespace http://tampermonkey.net/ // @version 1.01 // @description 川大自动抢课脚本 - 带有简单显示ui // @author Cloud Hypnos // @license GPL-3.0 // @match http://zhjw.scu.edu.cn/student/courseSelect/courseSelect/* // @match https://zhjw.scu.edu.cn/student/courseSelect/courseSelect/* // @grant none // @run-at document-end // ==/UserScript== (function() { 'use strict'; // 全局主题色配置 const THEME_COLORS = { primary: '#ff6f91', // 粉色主色调 primaryLight: '#ff8fa3', // 浅粉色 primaryDark: '#e6537c', // 深粉色 success: '#4caf50', // 成功色 error: '#f44336', // 错误色 warning: '#ff9800', // 警告色 info: '#2196f3', // 信息色 gray: '#6c757d' // 灰色 }; let isRunning = false; let searchInterval; let isDragging = false; let dragOffset = { x: 0, y: 0 }; let queryCount = 0; let notificationPermission = false; // 请求浏览器通知权限 async function requestNotificationPermission() { if (!("Notification" in window)) { console.log("此浏览器不支持桌面通知"); return false; } if (Notification.permission === "granted") { notificationPermission = true; return true; } if (Notification.permission !== "denied") { try { const permission = await Notification.requestPermission(); notificationPermission = permission === "granted"; if (notificationPermission) { console.log("通知权限已获取"); new Notification("选课助手已就绪", { body: "选课成功后将通过系统通知提醒您", icon: "https://www.scu.edu.cn/favicon.ico", tag: "course-assistant-ready" }); } return notificationPermission; } catch (error) { console.error("请求通知权限失败:", error); return false; } } return false; } // 发送系统通知 function sendNotification(title, body, isSuccess = true) { if (!notificationPermission) { console.log("通知权限未授予"); return; } try { const notification = new Notification(title, { body: body, icon: isSuccess ? "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%234caf50'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z'/%3E%3C/svg%3E" : "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23f44336'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z'/%3E%3C/svg%3E", requireInteraction: true, tag: `course-result-${Date.now()}`, vibrate: isSuccess ? [200, 100, 200] : [100, 50, 100] }); notification.onclick = function() { window.focus(); notification.close(); }; if (isSuccess) { playSuccessSound(); } } catch (error) { console.error("发送通知失败:", error); } } // 播放成功提示音 function playSuccessSound() { try { const audioContext = new (window.AudioContext || window.webkitAudioContext)(); const oscillator = audioContext.createOscillator(); const gainNode = audioContext.createGain(); oscillator.connect(gainNode); gainNode.connect(audioContext.destination); oscillator.frequency.setValueAtTime(800, audioContext.currentTime); oscillator.frequency.exponentialRampToValueAtTime(400, audioContext.currentTime + 0.3); gainNode.gain.setValueAtTime(0.3, audioContext.currentTime); gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.3); oscillator.start(audioContext.currentTime); oscillator.stop(audioContext.currentTime + 0.3); } catch (error) { console.log("无法播放提示音:", error); } } // 创建控制面板 function createButton() { const panel = document.createElement('div'); panel.id = 'autoGrabPanel'; panel.innerHTML = ` <div style="display: flex; border-radius: 5px; overflow: hidden; box-shadow: 0 3px 12px rgba(0,0,0,0.2);"> <!-- 左侧拖动区域 --> <div id="dragArea" style=" width: 20px; background: linear-gradient(135deg, ${THEME_COLORS.primary} 0%, ${THEME_COLORS.primaryDark} 100%); cursor: move; display: flex; align-items: center; justify-content: center; padding: 6px 2px; user-select: none; transition: all 0.2s ease; " title="拖动"> <svg width="10" height="10" viewBox="0 0 24 24" fill="white" opacity="0.8"> <path d="M10 9h4V6h3l-5-5-5 5h3v3zm-1 1H6V7l-5 5 5 5v-3h3v-4zm14 2l-5-5v3h-3v4h3v3l5-5zm-9 3h-4v3H7l5 5 5-5h-3v-3z"/> </svg> </div> <!-- 右侧功能区域 --> <div style=" background: white; padding: 6px 8px; min-width: 120px; border: 1px solid #e0e0e0; border-left: none; "> <!-- 查询计数 --> <div id="queryCounter" style=" margin-bottom: 5px; font-size: 10px; color: #666; display: flex; align-items: center; justify-content: space-between; "> <span>查询: <span id="queryCount" style="font-weight: 700; color: ${THEME_COLORS.primary};">0</span></span> <span id="timeElapsed" style="display: none;">运行: <span id="runTime">0</span>s</span> <span id="notificationBadge" style=" font-size: 8px; padding: 1px 3px; background: ${THEME_COLORS.success}; color: white; border-radius: 6px; display: none; ">通知</span> </div> <!-- 主按钮 --> <button id="actionBtn" style=" width: 100%; padding: 6px 8px; background: linear-gradient(135deg, ${THEME_COLORS.primary} 0%, ${THEME_COLORS.primaryDark} 100%); color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px; font-weight: 700; transition: all 0.2s ease; box-shadow: 0 2px 6px rgba(0,0,0,0.15); line-height: 1; "> 开始抢课 </button> <!-- 通知权限提示 --> <div id="notificationHint" style=" margin-top: 4px; padding: 4px 5px; background: #fff3cd; border: 1px solid #ffc107; border-radius: 3px; font-size: 9px; color: #856404; display: none; line-height: 1.2; "> 点击允许通知 </div> </div> </div> `; panel.style.cssText = ` position: fixed; top: 20px; right: 20px; z-index: 9999; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; `; document.body.appendChild(panel); setupDragEvents(panel); setupButtonEvents(); checkNotificationStatus(); } // 设置拖动事件 function setupDragEvents(panel) { const dragArea = document.getElementById('dragArea'); dragArea.onmouseenter = function() { if (!isDragging) { dragArea.style.background = `linear-gradient(135deg, ${THEME_COLORS.primaryLight} 0%, ${THEME_COLORS.primary} 100%)`; } }; dragArea.onmouseleave = function() { if (!isDragging) { dragArea.style.background = `linear-gradient(135deg, ${THEME_COLORS.primary} 0%, ${THEME_COLORS.primaryDark} 100%)`; } }; dragArea.onmousedown = function(e) { isDragging = true; const rect = panel.getBoundingClientRect(); dragOffset.x = e.clientX - rect.left; dragOffset.y = e.clientY - rect.top; dragArea.style.background = `linear-gradient(135deg, ${THEME_COLORS.primaryDark} 0%, ${THEME_COLORS.primary} 100%)`; dragArea.style.transform = 'scale(0.95)'; document.body.style.cursor = 'move'; const overlay = document.createElement('div'); overlay.id = 'dragOverlay'; overlay.style.cssText = ` position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 9998; cursor: move; `; document.body.appendChild(overlay); e.preventDefault(); }; document.onmousemove = function(e) { if (isDragging) { panel.style.left = (e.clientX - dragOffset.x) + 'px'; panel.style.top = (e.clientY - dragOffset.y) + 'px'; panel.style.right = 'auto'; } }; document.onmouseup = function(e) { if (isDragging) { isDragging = false; dragArea.style.background = `linear-gradient(135deg, ${THEME_COLORS.primary} 0%, ${THEME_COLORS.primaryDark} 100%)`; dragArea.style.transform = 'scale(1)'; document.body.style.cursor = 'auto'; const overlay = document.getElementById('dragOverlay'); if (overlay) { overlay.remove(); } } }; document.oncontextmenu = function(e) { if (isDragging) { isDragging = false; dragArea.style.background = `linear-gradient(135deg, ${THEME_COLORS.primary} 0%, ${THEME_COLORS.primaryDark} 100%)`; dragArea.style.transform = 'scale(1)'; document.body.style.cursor = 'auto'; const overlay = document.getElementById('dragOverlay'); if (overlay) { overlay.remove(); } e.preventDefault(); return false; } }; } // 设置按钮事件 function setupButtonEvents() { const actionBtn = document.getElementById('actionBtn'); actionBtn.onclick = function(e) { e.stopPropagation(); toggleAutoGrab(); }; actionBtn.onmouseenter = function() { if (!isRunning) { this.style.transform = 'translateY(-1px)'; this.style.boxShadow = '0 3px 8px rgba(0,0,0,0.15)'; } }; actionBtn.onmouseleave = function() { this.style.transform = 'translateY(0)'; this.style.boxShadow = '0 2px 6px rgba(0,0,0,0.15)'; }; } // 检查通知权限状态 function checkNotificationStatus() { if ("Notification" in window) { if (Notification.permission === "granted") { notificationPermission = true; document.getElementById('notificationBadge').style.display = 'inline-block'; } else if (Notification.permission === "default") { document.getElementById('notificationHint').style.display = 'block'; setTimeout(() => { requestNotificationPermission().then(granted => { if (granted) { document.getElementById('notificationBadge').style.display = 'inline-block'; document.getElementById('notificationHint').style.display = 'none'; } }); }, 2000); } } } // 更新按钮 function updateButton(text, color) { const actionBtn = document.getElementById('actionBtn'); if (actionBtn) { actionBtn.textContent = text; actionBtn.style.background = color.includes('gradient') ? color : `linear-gradient(135deg, ${color} 0%, ${color} 100%)`; } } // 更新查询计数和运行时间 let startTime = null; let timeInterval = null; function updateQueryCount() { queryCount++; const countElement = document.getElementById('queryCount'); if (countElement) { countElement.textContent = queryCount; } } function startTimer() { startTime = Date.now(); document.getElementById('timeElapsed').style.display = 'block'; timeInterval = setInterval(() => { if (startTime) { const elapsed = Math.floor((Date.now() - startTime) / 1000); document.getElementById('runTime').textContent = elapsed; } }, 1000); } function stopTimer() { startTime = null; if (timeInterval) { clearInterval(timeInterval); timeInterval = null; } document.getElementById('timeElapsed').style.display = 'none'; } function resetQueryCount() { queryCount = 0; const countElement = document.getElementById('queryCount'); if (countElement) { countElement.textContent = queryCount; } } // 查询课程并获取结果 function searchCourses() { return new Promise((resolve, reject) => { try { const iframe = document.querySelector("iframe"); if (!iframe || !iframe.contentWindow) { throw new Error('找不到iframe'); } const iframeWin = iframe.contentWindow; if (typeof iframeWin.guolv !== 'function') { throw new Error('找不到查询函数'); } const original$ = iframeWin.$; const originalAjax = original$.ajax; original$.ajax = function(options) { if (options.url && options.url.includes('/student/courseSelect/freeCourse/courseList')) { const originalSuccess = options.success; options.success = function(data) { original$.ajax = originalAjax; resolve(data); if (originalSuccess) { originalSuccess.call(this, data); } }; const originalError = options.error; options.error = function(xhr, status, error) { original$.ajax = originalAjax; reject(new Error(`查询失败: ${error}`)); if (originalError) { originalError.call(this, xhr, status, error); } }; } return originalAjax.call(this, options); }; iframeWin.guolv(1); setTimeout(() => { original$.ajax = originalAjax; reject(new Error('查询超时')); }, 8000); } catch (error) { reject(error); } }); } // 选中课程 function selectCourse(course) { try { const iframe = document.querySelector("iframe"); const iframeWin = iframe.contentWindow; if (typeof iframeWin.dealHiddenData !== 'function') { throw new Error('找不到选课函数'); } iframeWin.dealHiddenData(course, true); console.log(`选中课程: ${course.kcm} - ${course.skjs} (余量:${course.bkskyl})`); return true; } catch (error) { console.error('选中课程失败:', error); return false; } } // 提交选课 function submitCourse() { return new Promise((resolve, reject) => { // TODO resovle the CheckCode msg const yzmArea = $("#yzm_area"); if (yzmArea.length > 0 && yzmArea.css("display") !== "none" && !$("#submitCode").val()) { reject(new Error('请先输入验证码')); return; } const originalAjax = $.ajax; $.ajax = function(options) { if (options.url && options.url.includes('checkInputCodeAndSubmit')) { const originalSuccess = options.success; const originalError = options.error; options.success = function(data) { $.ajax = originalAjax; if (originalSuccess) originalSuccess.call(this, data); if (data.result === 'ok') { resolve('选课成功'); } else { reject(new Error(data.result)); } }; options.error = function(xhr) { $.ajax = originalAjax; if (originalError) originalError.call(this, xhr); reject(new Error(`提交失败: ${xhr.status}`)); }; } return originalAjax.call(this, options); }; try { if (typeof window.tijiao === 'function') { window.tijiao(); } else { throw new Error('找不到提交函数'); } } catch (error) { $.ajax = originalAjax; reject(error); } }); } // 主抢课逻辑 async function performGrab() { try { updateButton('查询中...', `linear-gradient(135deg, ${THEME_COLORS.warning} 0%, #f57c00 100%)`); updateQueryCount(); const data = await searchCourses(); if (!data || !data.rwRxkZlList || data.rwRxkZlList.length === 0) { updateButton('未找到课程', `linear-gradient(135deg, ${THEME_COLORS.gray} 0%, #495057 100%)`); return false; } const availableCourse = data.rwRxkZlList.find(course => course.bkskyl > 0); if (!availableCourse) { updateButton(`暂无余量`, `linear-gradient(135deg, ${THEME_COLORS.gray} 0%, #495057 100%)`); return false; } updateButton(`发现目标课程`, `linear-gradient(135deg, ${THEME_COLORS.success} 0%, #388e3c 100%)`); if (!selectCourse(availableCourse)) { updateButton('❌ 选中失败', `linear-gradient(135deg, ${THEME_COLORS.error} 0%, #d32f2f 100%)`); stopAutoGrab(); sendNotification( "选课失败", `无法选中课程: ${availableCourse.kcm}`, false ); return false; } await new Promise(resolve => setTimeout(resolve, 500)); updateButton('提交中...', `linear-gradient(135deg, ${THEME_COLORS.warning} 0%, #f57c00 100%)`); const result = await submitCourse(); updateButton(`✅ 选课成功`, `linear-gradient(135deg, ${THEME_COLORS.success} 0%, #388e3c 100%)`); sendNotification( "选课成功", `已成功选中: ${availableCourse.kcm}\n${availableCourse.skjs}`, true ); stopAutoGrab(); return true; } catch (error) { if (error.message.includes('验证码')) { updateButton('需要验证码', `linear-gradient(135deg, ${THEME_COLORS.warning} 0%, #f57c00 100%)`); sendNotification( "需要验证码", "请在页面上输入验证码后重新开始", false ); stopAutoGrab(); alert('请输入验证码后重新开始'); return false; } else { updateButton(`❌ 出现错误`, `linear-gradient(135deg, ${THEME_COLORS.error} 0%, #d32f2f 100%)`); sendNotification( "选课出错", error.message, false ); stopAutoGrab(); return false; } } } // 开始/停止抢课 function toggleAutoGrab() { if (isRunning) { stopAutoGrab(); } else { startAutoGrab(); } } // 开始自动抢课 function startAutoGrab() { isRunning = true; resetQueryCount(); startTimer(); console.log('[抢课脚本] 🚀 开始自动抢课'); updateButton('停止抢课', `linear-gradient(135deg, ${THEME_COLORS.error} 0%, #d32f2f 100%)`); performGrab().then(success => { if (!success && isRunning) { scheduleNextQuery(); } }); searchInterval = setInterval(async () => { if (!isRunning) { clearInterval(searchInterval); return; } const success = await performGrab(); if (!success && isRunning) { scheduleNextQuery(); } else if (!isRunning) { clearInterval(searchInterval); } }, Math.random() * 100 + 600); } // 安排下次查询 function scheduleNextQuery() { if (isRunning) { const delay = Math.random() * 10 + 10; updateButton('等待中...', `linear-gradient(135deg, ${THEME_COLORS.info} 0%, #1976d2 100%)`); setTimeout(() => { if (isRunning) { updateButton('查询中...', `linear-gradient(135deg, ${THEME_COLORS.info} 0%, #1976d2 100%)`); } }, delay); } } // 停止自动抢课 function stopAutoGrab() { isRunning = false; stopTimer(); updateButton('开始抢课', `linear-gradient(135deg, ${THEME_COLORS.primary} 0%, ${THEME_COLORS.primaryDark} 100%)`); if (searchInterval) { clearInterval(searchInterval); searchInterval = null; } console.log('[抢课脚本] 已停止'); } // 初始化 function init() { const checkReady = setInterval(() => { const iframe = document.querySelector("iframe"); if (iframe && iframe.contentWindow && document.querySelector('#myTab4')) { clearInterval(checkReady); setTimeout(() => { createButton(); console.log('[抢课脚本] 已就绪'); requestNotificationPermission(); }, 1000); } }, 1000); } // 启动 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();