From 512dd80496ca8a8e22945deb378697c15533a7f5 Mon Sep 17 00:00:00 2001 From: WONDER <208649912@qq.com> Date: Sat, 1 Aug 2026 21:14:17 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E9=80=80=E9=80=89?= =?UTF-8?q?=E5=A4=9A=E9=97=A8=E6=9B=BF=E6=8D=A2=E8=AF=BE=E7=A8=8B=EF=BC=88?= =?UTF-8?q?replaceCodes=20=E5=AD=97=E6=AE=B5=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新字段 replaceCodes: string[],取代原 replaceCode: string - 发现目标课有余量时依次退选所有替换课程,全部成功后再抢课 - 任意一门退选失败则中止本次,等待下次轮询 - UI 输入框支持逗号分隔多个课程号,与 timeFilter/teacherFilter 风格一致 - README 同步更新换课说明与代码示例 --- README.md | 16 +++++++++++-- courseGrabber.js | 60 +++++++++++++++++++++++++++++------------------- 2 files changed, 51 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 2bdf64c..6dd0a20 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,8 @@ https://www.douyin.com/video/7651581603861720441 ### 🤝 换课功能 - **情境**:如果你 pre 课A 但是又想有 课B 保底就想着能不能骑驴找马,在 课A 有空的时候把 课B 退了之后再去抢 - **实现**:这就是换课功能存在的意义,当你输入目标课程的时候下方有一个可选字段 “替换课程” ,那么就会帮你自动监听并实现上面的功能 -- **⚠️注意**:由于抢 课A 前需要先把 课B 退了,所以有 课B 退了之后 课A 没空位的风险,一般是对方和你一样用了本脚本 +- **多门替换**:替换课程支持填写多个课程号(逗号分隔),脚本会**依次退选**所有替换课程后再抢新课。适合需要同时腾出多个时间段的场景(如退掉两门课才能选上目标课) +- **⚠️注意**:由于抢 课A 前需要先把替换课程全部退了,若退选后目标课已无空位则等待下次机会;任意一门替换课退选失败时本次抢课中止 ### ⏰ 定时功能 - **定时开抢** - 设置精确的开抢时间 @@ -129,7 +130,8 @@ javascript:(function(){var script=document.createElement('script');script.src=' 2. 设置优先级(数字越小优先级越高) 3. 可选:设置时间过滤器(如:`星期一,第1-2节`) 4. 可选:设置教师过滤器(如:`叶利群,讲师`) -5. 点击"➕ 添加课程"按钮 +5. 可选:设置替换课程(如:`23306047` 或多门 `23306047,23306048`,逗号分隔,选中新课前依次退选) +6. 点击"➕ 添加课程"按钮 **方式二:修改代码配置** @@ -147,6 +149,16 @@ const TARGET_COURSES = [ timeFilter: ['星期一', '第1-2节'], teacherFilter: ['叶利群', '讲师'] }, + { + code: '23306048', + priority: 3, + replaceCodes: ['23306047'] // 选上此课前先退选一门 + }, + { + code: '23306049', + priority: 4, + replaceCodes: ['23306047', '23306050'] // 选上此课前依次退选两门 + }, ]; ``` diff --git a/courseGrabber.js b/courseGrabber.js index 3d2da3a..952de3e 100644 --- a/courseGrabber.js +++ b/courseGrabber.js @@ -1702,24 +1702,36 @@ // 获取课程配置 const courseConfig = TARGET_COURSES.find((c) => c.code === courseCode); - // 如果配置了替换课程,先执行退选 - if (courseConfig && courseConfig.replaceCode) { + // 如果配置了替换课程,先执行退选(支持多门) + const _replaceCodes = courseConfig && courseConfig.replaceCodes && courseConfig.replaceCodes.length > 0 + ? courseConfig.replaceCodes + : null; + if (_replaceCodes) { + const _codesList = _replaceCodes.join(", "); log( - `🔄 检测到需要替换课程 ${courseConfig.replaceCode},立即执行退选...`, + `🔄 检测到需要替换课程 ${_codesList},依次执行退选...`, "warning", courseCode, ); addUILog && addUILog( "warning", - `[${courseCode}] 🔄 发现空位!开始退选 ${courseConfig.replaceCode}`, + `[${courseCode}] 🔄 发现空位!开始退选 ${_codesList}`, ); - // 异步执行退选,然后选课 - dropCourse(courseConfig.replaceCode).then((dropSuccess) => { + // 依次退选所有替换课程,全部成功后再选新课 + const dropSequentially = (codes) => { + if (codes.length === 0) return Promise.resolve(true); + return dropCourse(codes[0]).then((ok) => { + if (!ok) return Promise.resolve(false); + return dropSequentially(codes.slice(1)); + }); + }; + + dropSequentially(_replaceCodes).then((dropSuccess) => { if (dropSuccess) { log( - `✅ 退选成功,立即选择新课程 ${courseCode}`, + `✅ 全部退选成功,立即选择新课程 ${courseCode}`, "success", courseCode, ); @@ -2783,8 +2795,8 @@
支持教师姓名或职称,满足任意一个即可
替换课程 (可选)
- -
选中新课程前,先退选此课程(用于换课)
+ +
多门课用逗号分隔,选中新课前依次退选这些课
@@ -2918,7 +2930,7 @@ const timeFilterEl = document.getElementById("cg-time-filter"); const teacherFilterEl = document.getElementById("cg-teacher-filter"); - const replaceCode = (replaceCodeEl ? replaceCodeEl.value : "").trim(); + const replaceCodeInput = (replaceCodeEl ? replaceCodeEl.value : "").trim(); const timeFilterInput = (timeFilterEl ? timeFilterEl.value : "").trim(); const teacherFilterInput = ( teacherFilterEl ? teacherFilterEl.value : "" @@ -2926,7 +2938,8 @@ // 构造最终要推入的课程对象,避免作用域或外部修改影响 const finalCourse = { code: code, priority: priority }; - if (replaceCode) finalCourse.replaceCode = replaceCode; + const finalReplaceCodes = safeParseFilterInput(replaceCodeInput); + if (finalReplaceCodes.length > 0) finalCourse.replaceCodes = finalReplaceCodes; // 使用安全的解析函数处理过滤器输入(避免被篡改的 Array.prototype.filter) const finalTimeFilter = safeParseFilterInput(timeFilterInput); @@ -2952,8 +2965,8 @@ updateCourseList(); let logMsg = `已添加课程: ${code} (优先级: ${priority})`; - if (finalCourse.replaceCode) - logMsg += ` [替换: ${finalCourse.replaceCode}]`; + if (finalCourse.replaceCodes && finalCourse.replaceCodes.length > 0) + logMsg += ` [替换: ${finalCourse.replaceCodes.join(", ")}]`; if (finalCourse.timeFilter && finalCourse.timeFilter.length > 0) { logMsg += ` [时间过滤: ${finalCourse.timeFilter.join(", ")}]`; } @@ -3035,13 +3048,13 @@ list.innerHTML = TARGET_COURSES.map((course, index) => { const hasConfig = - course.replaceCode || course.timeFilter || course.teacherFilter; + (course.replaceCodes && course.replaceCodes.length > 0) || course.timeFilter || course.teacherFilter; let filterHTML = ""; if (hasConfig) { filterHTML = '
'; - if (course.replaceCode) { - filterHTML += `
🔄 替换:${course.replaceCode}
`; + if (course.replaceCodes && course.replaceCodes.length > 0) { + filterHTML += `
🔄 替换:${course.replaceCodes.join(", ")}
`; } if (course.timeFilter) { filterHTML += `
⏰ 时间:${course.timeFilter.join(", ")}
`; @@ -3084,12 +3097,12 @@ window.editCourseUI = (index) => { const course = TARGET_COURSES[index]; - const replaceCode = prompt( - `编辑课程 ${course.code} 的替换课程\n\n输入要替换的课程号,留空表示不替换\n例如: 23306047`, - course.replaceCode || "", + const replaceCodeInput = prompt( + `编辑课程 ${course.code} 的替换课程\n\n多门课用逗号分隔,留空表示不替换\n例如: 23306047,23306048`, + course.replaceCodes ? course.replaceCodes.join(",") : "", ); - if (replaceCode === null) return; // 用户取消 + if (replaceCodeInput === null) return; // 用户取消 const timeFilter = prompt( `编辑课程 ${course.code} 的时间过滤器\n\n多个关键词用逗号分隔,留空表示不过滤\n例如: 星期一,第1-2节`, @@ -3106,10 +3119,11 @@ if (teacherFilter === null) return; // 用户取消 // 更新课程配置 - if (replaceCode.trim()) { - course.replaceCode = replaceCode.trim(); + const parsedReplaceCodes = safeParseFilterInput(replaceCodeInput); + if (parsedReplaceCodes.length > 0) { + course.replaceCodes = parsedReplaceCodes; } else { - delete course.replaceCode; + delete course.replaceCodes; } // 使用安全的解析函数处理过滤器输入(避免被篡改的 Array.prototype.filter)