${line}
`) .join(""); try { const ueditor = this._window.UE.getEditor(textareaElement.name); if (ueditor && typeof ueditor.setContent === "function") { ueditor.setContent(htmlContent); } else { textareaElement.value = answerText; textareaElement.dispatchEvent(new Event("input", { bubbles: true })); textareaElement.dispatchEvent(new Event("change", { bubbles: true })); textareaElement.dispatchEvent(new KeyboardEvent("keydown", { bubbles: true })); textareaElement.dispatchEvent(new KeyboardEvent("keyup", { bubbles: true })); } } catch (e) { textareaElement.value = answerText; textareaElement.dispatchEvent(new Event("input", { bubbles: true })); textareaElement.dispatchEvent(new Event("change", { bubbles: true })); textareaElement.dispatchEvent(new KeyboardEvent("keydown", { bubbles: true })); textareaElement.dispatchEvent(new KeyboardEvent("keyup", { bubbles: true })); } } else if (question.type === "13") { const sortSelects = question.element.querySelectorAll(".sortQuesSelect .dept_select"); if (sortSelects.length === 0) return; let answers = question.answer; if (typeof question.answer === 'string') { if (question.answer.startsWith('[')) { try { answers = JSON.parse(question.answer); } catch (e) { answers = question.answer.split(/[,,]/).map(a => a.trim()).filter(a => a); } } else { answers = question.answer.split(/[,,]/).map(a => a.trim()).filter(a => a); } } sortSelects.forEach((select, index) => { if (index >= answers.length) return; const answerValue = answers[index].toUpperCase(); select.value = answerValue; const chosenContainer = select.nextElementSibling; if (chosenContainer && chosenContainer.classList.contains("chosen-container")) { const chosenSingle = chosenContainer.querySelector(".chosen-single span"); if (chosenSingle) { chosenSingle.textContent = answerValue; } select.dispatchEvent(new Event("change", { bubbles: true })); const chosenResults = chosenContainer.querySelectorAll(".chosen-results li"); chosenResults.forEach(li => { li.classList.remove("result-selected"); if (li.textContent.trim() === answerValue) { li.classList.add("result-selected"); } }); } }); const answerInput = question.element.querySelector('input[name^="answer"]'); if (answerInput) { answerInput.value = answers.join(""); } } } catch (error) { this.addLog(`答题过程发生错误:${error.message}`, "danger"); } finally { this.isFilling = false; } }); this.type = type; if (iframe) { this._document = iframe.contentDocument; this._window = iframe.contentWindow; decodeCipherFont(this._document); } else { decodeCipherFont(this._document); } } extractOptions(optionElements, optionSelector) { const optionsObject = {}; const optionTexts = []; optionElements.forEach((optionElement) => { var _a; const optionTextContent = this.stripTags(((_a = optionElement.querySelector(optionSelector)) == null ? void 0 : _a.innerHTML) || ""); // 存储外层容器元素 - 用于验证选中状态(aria-checked)和点击 optionsObject[optionTextContent] = optionElement; optionTexts.push(optionTextContent); }); return [optionsObject, optionTexts]; } addQuestions(questionElements) { questionElements.forEach((questionElement) => { var _a, _b, _c, _d; let questionTitle = ""; let questionTypeText = ""; let optionElements = []; let optionsObject = {}; let optionTexts = []; if (["zy", "ks"].includes(this.type)) { const h3Element = questionElement.querySelector("h3"); const colorShallowElement = questionElement.querySelector(".colorShallow"); if (["zy"].includes(this.type)) { questionTypeText = (questionElement == null ? void 0 : questionElement.getAttribute("typename")) || ""; } else if (["ks"].includes(this.type)) { questionTypeText = colorShallowElement ? this.stripTags(colorShallowElement.outerHTML).slice(1, 4) : ""; } const fullText = h3Element ? h3Element.textContent : ""; const typeText = colorShallowElement ? colorShallowElement.textContent : ""; questionTitle = fullText.replace(typeText, "").replace(/^\d+\.\s*/, "").replace(/_+/g, "").trim(); optionElements = questionElement.querySelectorAll(SELECTORS.CX_OPTION_ZY_KS); if (!optionElements.length) { optionElements = questionElement.querySelectorAll('.answerBg, .answer_p, ul li, .option, .option-list li'); } [optionsObject, optionTexts] = this.extractOptions(optionElements, ".answer_p"); } else if (["zj"].includes(this.type)) { questionTitle = this.stripTags(((_c = questionElement.querySelector(".fontLabel")) == null ? void 0 : _c.innerHTML) || ""); questionTypeText = this.stripTags(((_d = questionElement.querySelector(".newZy_TItle")) == null ? void 0 : _d.innerHTML) || ""); optionElements = questionElement.querySelectorAll(SELECTORS.CX_OPTION_ZJ); if (!optionElements.length) { optionElements = questionElement.querySelectorAll('[class*="before-after"], ul li, .option, .option-list li, label:not(.before)'); } [optionsObject, optionTexts] = this.extractOptions(optionElements, ".fl.after"); if (!questionTitle) { const titleEl = questionElement.querySelector('.Zy_TItle, .clearfix, h3, .question-title'); if (titleEl) { questionTitle = this.stripTags(titleEl.innerHTML || titleEl.textContent || ""); } } } // 增强型题型识别:结合文本关键词和DOM元素特征 let questionType = "999"; let typeIdentifyLog = []; // 步骤1:从文本中提取题型关键词(优先) const cleanedTypeText = questionTypeText.replace(/[\[\]【】]/g, ""); if (this.typeMap.has(cleanedTypeText)) { questionType = this.typeMap.get(cleanedTypeText); typeIdentifyLog.push(`文本关键词(typename): ${cleanedTypeText} -> ${questionType}`); } else if (questionTitle.match(/[\[\【](.+?)[\]\】]/)) { const matchedType = questionTitle.match(/[\[\【](.+?)[\]\】]/)[1]; if (this.typeMap.has(matchedType)) { questionType = this.typeMap.get(matchedType); typeIdentifyLog.push(`文本关键词(标题): ${matchedType} -> ${questionType}`); } } // 获取所有 DOM 元素计数(不管文本识别是否成功) const radioCount = questionElement.querySelectorAll('input[type="radio"]').length; const checkboxCount = questionElement.querySelectorAll('input[type="checkbox"]').length; const textareaCount = questionElement.querySelectorAll('textarea').length; const inputTextCount = questionElement.querySelectorAll('input[type="text"]').length; // 步骤2:DOM元素计数(仅在步骤1无法确定时使用) // radio=2 → 判断题, radio>2 → 单选题, checkbox>2 → 多选题, textarea≥1 → 填空题 if (questionType === "999") { typeIdentifyLog.push(`DOM计数: radio=${radioCount}, checkbox=${checkboxCount}, textarea=${textareaCount}`); if (textareaCount >= 1) { questionType = "2"; // 填空题 typeIdentifyLog.push(`DOM(textarea≥1) -> ${questionType}`); } else if (radioCount === 2) { questionType = "3"; // 判断题 (恰好2个radio) typeIdentifyLog.push(`DOM(radio=2) -> ${questionType}`); } else if (checkboxCount >= 2) { questionType = "1"; // 多选题 (2个以上checkbox) typeIdentifyLog.push(`DOM(checkbox≥2) -> ${questionType}`); } else if (radioCount > 2) { questionType = "0"; // 单选题 (3个以上radio) typeIdentifyLog.push(`DOM(radio>2) -> ${questionType}`); } } else { // 文本识别成功,仍然记录 DOM 计数供调试 typeIdentifyLog.push(`DOM计数: radio=${radioCount}, checkbox=${checkboxCount}`); } // DOM判断失败时,用文本+特征兜底 if (questionType === "999") { const hasSortSelect = questionElement.querySelector('.sortQuesSelect') !== null; if (hasSortSelect) { questionType = "13"; // 排序题 } else if (inputTextCount > 0 && Object.keys(optionsObject).length === 0) { questionType = "2"; // 填空题(文本输入) } else if (optionsTexts.length === 2 && (optionsTexts.some(o => /^(对|正确|是|true|√)$/i.test(o.trim())) && optionsTexts.some(o => /^(错|错误|否|false|×)$/i.test(o.trim())))) { questionType = "3"; // 判断题(对/错) } else if (optionsTexts.length >= 4) { questionType = "0"; // 默认单选题(4个选项) } else { questionType = "0"; // 最后兜底 } typeIdentifyLog.push(`兜底 -> ${questionType}`); } // 记录识别过程(在后面添加到 questions 对象中) const identifyInfo = { title: questionTitle.substring(0, 50) + '...', type: questionType, log: typeIdentifyLog.join('; ') }; this.questions.push({ element: questionElement, type: questionType, title: this.trimTitle(questionTitle), optionsText: optionTexts, options: optionsObject, answer: [], workType: this.type, refer: this._window.location.href, identifyInfo: identifyInfo // 保存识别信息供调试 }); }); } } const useCxChapterLogic = () => { const logStore = useLogStore(); const progressStore = useProgressStore(); const getCookieLocal = (name) => { const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)')); return match ? match[2] : ''; }; let cachedUid = ''; if (_unsafeWindow?.getCookie) { cachedUid = _unsafeWindow.getCookie("UID"); } if (!cachedUid) { cachedUid = getCookieLocal('UID'); } if (!cachedUid) { cachedUid = getCookieLocal('_uid'); } if (!cachedUid && _unsafeWindow?.uid) { cachedUid = _unsafeWindow.uid; } const init = () => { const currentUrl = window.location.href; if (!currentUrl.includes("&mooc2=1")) { window.location.href = currentUrl + "&mooc2=1"; } logStore.addLog(`检测到用户进入到章节学习页面`, "primary"); logStore.addLog(`正在解析任务点,请稍等5-10秒(如果长时间没有反应,请刷新页面)`, "warning"); }; const configStore = useConfigStore(); let _justClickedNext = false; let _currentIframe = null; let _urlBackupTimer = null; const checkUnansweredAssignments = async (documentElement) => { try{ const allIframes = FrameScanner.collectDeepSync(documentElement); for(const iframe of allIframes){ const src = iframe.src || ''; const _src = iframe.getAttribute('_src') || ''; // 增强版检测逻辑:覆盖更多答题入口模式 const isAssignment = src.includes('api/work') || _src.includes('api/work') || src.includes('work') || src.includes('exam') || src.includes('test') || src.includes('quiz') || src.includes('zuoye') || src.includes('kaoshi') || src.includes('answer') || src.includes('dowork') || src.includes('doTest') || src.includes('calcAnswer') || src.includes('work/dowork') || src.includes('work/doTest') || src.includes('mooc2/work') || src.includes('work/index') || src.includes('work/list') || _src.includes('work') || _src.includes('exam') || _src.includes('test') || _src.includes('quiz') || _src.includes('answer'); if(isAssignment){ try{ const ansJobIcon = iframe.parentElement?.querySelector('.ans-job-icon'); if(ansJobIcon){ const ariaLabel = ansJobIcon.getAttribute('aria-label') || ''; const titleAttr = ansJobIcon.getAttribute('title') || ''; const textContent = ansJobIcon.textContent || ''; // 检查是否已完成 - 增强检测 const isCompleted = ariaLabel.includes('已完成') || titleAttr.includes('已完成') || textContent.includes('已完成') || ariaLabel.includes('100%') || titleAttr.includes('100%'); if(isCompleted){ continue; } // 未完成的作业 logStore.addLog(`发现未完成的作业/测验任务点: ${ariaLabel || titleAttr || '未知状态'}`, "warning"); return true; }else{ const parent = iframe.parentElement; const jobWrapper = parent?.querySelector('.ans-job-wrapper, .job-wrapper'); if(jobWrapper && !jobWrapper.classList.contains('complete')){ logStore.addLog(`发现未标记完成的作业/测验任务点`, "warning"); return true; } } }catch(e){ // 跨域 iframe 无法访问,假设未完成 logStore.addLog(`发现作业/测验任务点(跨域无法检测状态)`, "warning"); return true; } } } // 第二轮检测:通过DOM元素查找答题入口 const assignmentSelectors = [ '.ans-job-icon:not([aria-label*="已完成"])', '.ans-job-icon:not([title*="已完成"])', '[class*="job"]:not([class*="complete"])', '[class*="work"]:not([class*="complete"])', '[class*="exam"]:not([class*="complete"])', '[class*="quiz"]:not([class*="complete"])', 'a[href*="work"]:not([class*="complete"])', 'a[href*="exam"]:not([class*="complete"])', 'a[href*="test"]:not([class*="complete"])', 'a[href*="answer"]:not([class*="complete"])', '.jobclass:not(.complete)', '.work-icon:not(.complete)', '.exam-icon:not(.complete)', '.assignment-icon:not(.complete)', '.ans-job:not(.complete)', '.ans-job-btn:not(.complete)', // 新增:黄色数字标记选择器(章节旁边的橙色/黄色圆形数字标记) '.orangeIcon', '.orange-icon', '.numIcon', '.icon-num', '.posCatalog_select:has(.jobUnfinishCount)', '.posCatalog_item:has(.jobUnfinishCount)', '[style*="background-color:#f5a623"]', '[style*="background-color:orange"]', '[style*="background-color:#FFA500"]', '.catalog-num', '.chapter-num' ]; for(const selector of assignmentSelectors){ try{ const elements = documentElement.querySelectorAll(selector); for(const el of elements){ const ariaLabel = el.getAttribute('aria-label') || ''; const titleAttr = el.getAttribute('title') || ''; const textContent = el.textContent || ''; // 检查是否包含"未完成"、"待完成"等关键词 const isUnfinished = ariaLabel.includes('未完成') || titleAttr.includes('未完成') || textContent.includes('未完成') || ariaLabel.includes('待完成') || titleAttr.includes('待完成') || textContent.includes('待完成') || (!ariaLabel.includes('已完成') && !titleAttr.includes('已完成')); if(isUnfinished && el.offsetParent !== null){ const rect = el.getBoundingClientRect(); if(rect.width > 0 && rect.height > 0){ logStore.addLog(`通过DOM选择器发现未完成的答题入口: ${selector}`, "warning"); return true; } } } }catch(e){ // 忽略无效选择器 } } // 第三轮检测:通过文本内容查找 const allElements = documentElement.querySelectorAll('a, button, span, div'); for(const el of allElements){ const text = el.textContent || ''; const href = el.getAttribute('href') || ''; // 检查是否包含答题相关关键词且不包含"已完成" if(/作业|测验|考试|答题|work|exam|test|quiz/.test(text) && !text.includes('已完成') && !text.includes('100%') && el.offsetParent !== null && el.getBoundingClientRect().width > 0 && el.getBoundingClientRect().height > 0){ // 检查链接是否指向答题页面 if(href.includes('work') || href.includes('exam') || href.includes('test') || href.includes('answer')){ logStore.addLog(`通过文本发现未完成的答题入口`, "warning"); return true; } } } // 第四轮检测:检查是否有作业/测验iframe但无法访问其内容(跨域) for(const iframe of allIframes){ const src = iframe.src || ''; const _src = iframe.getAttribute('_src') || ''; // 如果iframe的src包含作业相关关键词,但无法访问其内容 if((src.includes('work') || src.includes('exam') || src.includes('test') || src.includes('quiz') || _src.includes('work') || _src.includes('exam')) && !src.includes('video') && !src.includes('audio')){ try{ // 尝试访问iframe的内容,如果失败说明是跨域的 const contentDoc = iframe.contentDocument || iframe.contentWindow.document; if(!contentDoc){ logStore.addLog(`发现跨域的作业/测验iframe,假设未完成`, "warning"); return true; } }catch(e){ // 跨域访问失败,假设未完成 logStore.addLog(`发现跨域的作业/测验iframe,假设未完成`, "warning"); return true; } } } // 第五轮检测:专门检测黄色/橙色数字标记(章节列表中未完成测验的标记) const numberBadgeSelectors = [ '.catalog_num', '.catalog-num', '.chapter-num', '.section-num', '.posCatalog_num', '.orangeNum', '.yellowNum', '[class*="num"]:not([class*="icon"])', '.icon-num', '.num-icon', // 新增:学习通特有的数字标记选择器 '.catalogNum', '.catalog-num-icon', '.chapterNum', '.sectionNum', '.posCatalogNum', '.lesson-num', '.lessonNum', '.iconNum', '.numIcon', '[class*="catalog"] [class*="num"]', '[class*="chapter"] [class*="num"]', '[class*="section"] [class*="num"]', '[class*="lesson"] [class*="num"]', // 圆形徽章选择器 '.badge-num', '.badge-circle', '.circle-badge', '.num-circle', '.circle-num', // 学习通章节列表特定选择器 '.posCatalog_item .orangeIcon', '.posCatalog_select .orangeIcon', '.chapter-item .orangeIcon', '.section-item .orangeIcon', '.posCatalog_item .icon_yellow', '.posCatalog_select .icon_yellow', '.chapter-item .icon_yellow', '.section-item .icon_yellow', // ========== 新增:学习通章节列表特有选择器 ========== // 章节列表容器 '.posCatalog', '.posCatalog_list', '.posCatalog_content', '.chapter-list', '.section-list', '.lesson-list', '.course-tree', '.course-chapter', // 章节列表项(包含数字标记的父元素) '.posCatalog_select', '.posCatalog_item', '.posCatalog li', '.chapterItem', '.chapter-item', '.sectionItem', '.section-item', '.lessonItem', '.lesson-item', // 学习通移动端章节列表 '.m-chapter', '.m-section', '.m-lesson', '.mobile-chapter', '.mobile-section', // 图标选择器 '.icon_homework', '.icon_exam', '.icon_test', '.icon_quiz', '.icon_task', '.icon_work', '[class*="icon_homework"]', '[class*="icon_exam"]', '[class*="icon_test"]', '[class*="icon_quiz"]', '[class*="icon_task"]', '[class*="icon_work"]', // 学习通数据属性选择器 '[data-role="chapter"]', '[data-role="section"]', '[data-role="lesson"]', '[data-id^="chapter"]', '[data-id^="section"]', '[data-id^="lesson"]', '[data-type="chapter"]', '[data-type="section"]', '[data-type="lesson"]', '[data-type="homework"]', '[data-type="quiz"]', '[data-type="test"]', '[data-type="exam"]' ]; for(const selector of numberBadgeSelectors){ try{ const elements = documentElement.querySelectorAll(selector); for(const el of elements){ const text = el.textContent || ''; const style = el.getAttribute('style') || ''; const className = el.className || ''; // 检查是否是数字标记(通常是1-9的数字) if(/^\d+$/.test(text.trim()) && parseInt(text.trim()) > 0){ // 检查是否是黄色/橙色标记 const isOrangeYellow = style.includes('orange') || style.includes('#f5a623') || style.includes('#FFA500') || style.includes('#FFC107') || className.includes('orange') || className.includes('yellow'); if(isOrangeYellow){ logStore.addLog(`发现黄色/橙色数字标记: ${text},表示有未完成的任务`, "warning"); return true; } // 如果是数字标记,但没有颜色,检查父元素是否有作业相关的类名 const parent = el.parentElement; if(parent && (parent.className.includes('job') || parent.className.includes('work') || parent.className.includes('exam') || parent.className.includes('quiz'))){ logStore.addLog(`发现作业/测验数字标记: ${text}`, "warning"); return true; } // 检查父元素是否为学习通章节项 const chapterItem = el.closest('.posCatalog_select, .posCatalog_item, .chapterItem, .chapter-item, .sectionItem, .section-item, .lessonItem, .lesson-item'); if(chapterItem){ // 检查章节项是否有未完成标记 const isCompleted = chapterItem.classList.contains('completed') || chapterItem.classList.contains('icon_Completed') || chapterItem.querySelector('.icon_Completed') || chapterItem.getAttribute('aria-label')?.includes('已完成') || chapterItem.textContent?.includes('已完成'); if(!isCompleted){ logStore.addLog(`发现未完成的章节项,包含数字标记: ${text}`, "warning"); return true; } } // 检查祖先元素是否有作业相关的类名 let ancestor = el.parentElement; for(let i = 0; i < 5 && ancestor; i++){ if(ancestor.className.includes('posCatalog') || ancestor.className.includes('chapter') || ancestor.className.includes('section') || ancestor.className.includes('lesson') || ancestor.className.includes('task')){ logStore.addLog(`发现章节/任务列表中的数字标记: ${text}`, "warning"); return true; } ancestor = ancestor.parentElement; } } } }catch(e){ // 忽略无效选择器 } } // 第六轮检测:专门检测学习通章节列表中的测验入口 // 学习通章节列表结构通常是:.posCatalog > li > .posCatalog_select/.posCatalog_item const posCatalogItems = documentElement.querySelectorAll('.posCatalog li, .posCatalog_select, .posCatalog_item'); for(const item of posCatalogItems){ try{ // 检查是否有未完成标记(黄色/橙色徽章) const hasOrangeIcon = item.querySelector('.orangeIcon, .icon_yellow, [class*="orange"], [class*="yellow"]'); const hasNumBadge = item.querySelector('[class*="num"]:not([class*="icon"])'); if(hasOrangeIcon || hasNumBadge){ // 检查是否已完成 const isCompleted = item.classList.contains('completed') || item.querySelector('.icon_Completed') || item.textContent?.includes('已完成') || item.getAttribute('aria-label')?.includes('已完成'); if(!isCompleted){ // 检查是否包含测验相关的子元素 const hasQuizIcon = item.querySelector('.icon_homework, .icon_exam, .icon_test, .icon_quiz'); const hasQuizText = item.textContent?.includes('测验') || item.textContent?.includes('作业') || item.textContent?.includes('考试'); if(hasQuizIcon || hasQuizText){ logStore.addLog(`发现未完成的章节测验任务点`, "warning"); return true; } // 如果有数字标记,假设是未完成的任务 if(hasNumBadge){ const numText = hasNumBadge.textContent || ''; if(/^\d+$/.test(numText.trim()) && parseInt(numText.trim()) > 0){ logStore.addLog(`发现章节列表中的数字标记: ${numText},表示有未完成的任务`, "warning"); return true; } } } } }catch(e){ // 忽略异常 } } // 第七轮检测:通过颜色和形状检测黄色圆形标记 const allSpans = documentElement.querySelectorAll('span, i, div'); for(const el of allSpans){ const style = window.getComputedStyle ? window.getComputedStyle(el) : el.currentStyle; if(!style) continue; const bgColor = style.backgroundColor || style.background; const borderRadius = style.borderRadius || ''; // 检查是否是圆形(borderRadius约为50%)且背景色是黄色/橙色 const isCircle = borderRadius && (borderRadius.includes('50%') || borderRadius.includes('100%') || borderRadius.includes('circle')); const isYellowOrange = bgColor && (bgColor.includes('orange') || bgColor.includes('250, 166, 35') || // #f5a623 bgColor.includes('255, 165, 0') || // #FFA500 bgColor.includes('255, 193, 7')); // #FFC107 if(isCircle && isYellowOrange){ const text = el.textContent || ''; if(/^\d+$/.test(text.trim()) && parseInt(text.trim()) > 0){ logStore.addLog(`发现黄色圆形数字标记: ${text},表示有未完成的任务`, "warning"); return true; } } } return false; }catch(e){ console.warn('[checkUnansweredAssignments] 检测异常:', e); return false; } }; const monitorIframes = () => { if (_urlBackupTimer) { clearTimeout(_urlBackupTimer); _urlBackupTimer = null; } const documentElement = document.documentElement; const iframe = documentElement.querySelector("iframe"); if (!iframe) { setTimeout(() => monitorIframes(), 2000); return; } const currentSrc = iframe.src || ''; const srcChanged = currentSrc !== _lastIframeSrc; if (iframe !== _currentIframe) { _currentIframe = iframe; iframe.addEventListener("load", function onIframeLoad() { if (_urlBackupTimer) { clearTimeout(_urlBackupTimer); _urlBackupTimer = null; } monitorIframes(); }); } try { if (iframe.contentDocument && iframe.contentDocument.readyState === 'complete') { if (srcChanged) { _lastIframeSrc = currentSrc; watchIframe(documentElement); } } } catch (e) { } }; const watchUrlChanges = () => { let currentUrl = window.location.href; const afkEnabled = configStore.platformParams.cx.parts[4]?.params[0]?.value || false; const checkUrlChange = () => { if (currentUrl !== window.location.href) { currentUrl = window.location.href; _justClickedNext = false; _globalTaskId++; if (_urlBackupTimer) { clearTimeout(_urlBackupTimer); } _urlBackupTimer = setTimeout(() => { _urlBackupTimer = null; monitorIframes(); }, 2000); } }; if (afkEnabled) { BackgroundWorker.start('url_watcher', checkUrlChange, 2000); } else { setInterval(checkUrlChange, 2000); } }; let _globalTaskId = 0; let hasLoggedSkipTip = false; let _currentWatchSubscription = null; const _processedIframeTasks = new WeakMap(); let _lastIframeSrc = null; window.__getAnswerTaskId__ = () => _globalTaskId; const watchIframe = async (documentElement) => { if (_currentWatchSubscription) { _currentWatchSubscription.unsubscribe(); _currentWatchSubscription = null; } forceStopSimulatePlay(); const thisTaskId = ++_globalTaskId; hasLoggedSkipTip = false; const waitForQuizPage = async (timeout = 15000) => { if (typeof AutoTaskScheduler !== 'undefined' && typeof AutoTaskScheduler._waitForQuizPage === 'function') { return AutoTaskScheduler._waitForQuizPage(timeout); } logStore.addLog(`答题页面检测方法不可用`, "warning"); return false; }; const autoAnswerQuiz = async () => { if (typeof AutoTaskScheduler !== 'undefined' && typeof AutoTaskScheduler._autoAnswer === 'function') { return AutoTaskScheduler._autoAnswer(); } logStore.addLog(`自动答题方法不可用`, "warning"); return false; }; const submitQuiz = async () => { if (typeof AutoTaskScheduler !== 'undefined' && typeof AutoTaskScheduler._submitQuiz === 'function') { return AutoTaskScheduler._submitQuiz(); } logStore.addLog(`提交答题方法不可用`, "warning"); return false; }; const fallbackToQuizUrl = async () => { if (typeof AutoTaskScheduler !== 'undefined' && typeof AutoTaskScheduler._fallbackToQuizUrl === 'function') { return AutoTaskScheduler._fallbackToQuizUrl(); } logStore.addLog(`兜底跳转方法不可用`, "warning"); return false; }; const findChapterQuizTabInDoc = (doc) => { try { const direct = doc.querySelector('option[value="章节测验"], li[title*="章节测验"], a[title*="章节测验"], button[title*="章节测验"], [role="tab"][title*="章节测验"]'); if (direct) return direct; const onclickTarget = Array.from(doc.querySelectorAll('li[onclick], a[onclick], button[onclick], span[onclick], div[onclick]')).find(el => { const onclick = String(el.getAttribute('onclick') || ''); return /changeDisplayContent|getTeacherAjax|chapter|job|work|quiz/i.test(onclick) && /章节测验|测验|作业|quiz|work/i.test((el.textContent || el.getAttribute('title') || '') + onclick); }); if (onclickTarget) return onclickTarget; return Array.from(doc.querySelectorAll('li, a, button, span, div, [role="tab"], [role="option"]')).find(el => { const text = String(el.textContent || el.getAttribute('title') || '').trim(); if (!/章节测验|测验/.test(text)) return false; const rect = typeof el.getBoundingClientRect === 'function' ? el.getBoundingClientRect() : { width: 1, height: 1 }; return rect.width > 0 && rect.height > 0; }) || null; } catch(e) { return null; } }; const getSameOriginDocs = () => { const docs = [document]; for (const iframe of document.querySelectorAll('iframe')) { try { const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document; if (iframeDoc) docs.push(iframeDoc); } catch(e) {} } return docs; }; const hasActiveUnfinishedVideoTask = () => { const docs = getSameOriginDocs(); for (const doc of docs) { try { const videos = Array.from(doc.querySelectorAll('video')); for (const video of videos) { const duration = Number(video.duration || 0); const currentTime = Number(video.currentTime || 0); if (duration > 0 && currentTime / duration < 0.9 && !video.ended) return true; if (duration === 0 && !video.ended) return true; } const text = String(doc.body?.innerText || doc.body?.textContent || ''); const hasVideoTab = /\b1\s*视频|视频/.test(text); const hasVideoRequirement = /观看时长|总时长|90%|任务点/.test(text); const hasCompleted = /任务点已完成|已完成|100%/.test(text); const hasQuizPage = /我的答案|单选题|多选题|判断题|填空题|本题得分/.test(text); if (hasVideoTab && hasVideoRequirement && !hasCompleted && !hasQuizPage) return true; } catch(e) {} } return false; }; const getCxStudyChapters = () => { const docs = getSameOriginDocs(); const chapters = []; for (const doc of docs) { try { const nodes = Array.from(doc.querySelectorAll('[onclick*="getTeacherAjax"]')); for (const el of nodes) { const onclick = String(el.getAttribute('onclick') || ''); const match = onclick.match(/getTeacherAjax\(['"](.+?)['"],\s*['"](.+?)['"],\s*['"](.+?)['"]\)/); if (!match) continue; const chapterId = match[3]; const container = el.closest('.posCatalog_select, .posCatalog_item, li, .chapterItem, .chapter-item') || el.parentElement; const countEl = container?.querySelector('.jobUnfinishCount, input.jobUnfinishCount'); const rawCount = countEl ? (countEl.value || countEl.textContent || countEl.getAttribute('value') || '0') : '0'; const unFinishCount = parseInt(String(rawCount).replace(/\D/g, ''), 10) || 0; chapters.push({ doc, element: el, container, courseId: match[1], classId: match[2], chapterId, unFinishCount }); } } catch(e) {} } return chapters; }; const tryCxStudyDispatcher = async () => { if (!/\/mycourse\/studentstudy/.test(location.href)) return false; const chapters = getCxStudyChapters(); const target = chapters.find(chapter => chapter.unFinishCount > 0); if (!target) return false; logStore.addLog(`按章节目录定位到未完成章节(${target.chapterId}),未完成任务数: ${target.unFinishCount}`, "info"); try { const targetWindow = (typeof unsafeWindow !== 'undefined') ? unsafeWindow : window; if (!target.container?.classList?.contains('posCatalog_active')) { if (typeof targetWindow.getTeacherAjax === 'function') { targetWindow.getTeacherAjax(target.courseId, target.classId, target.chapterId); } else { safeClick(target.element); } logStore.addLog(`已切换到未完成章节,等待内容加载`, "info"); await new Promise(resolve => setTimeout(resolve, 2500)); } if (target.container) { try { target.container.scrollIntoView({ behavior: "smooth", block: "center" }); } catch(e) {} } if (hasActiveUnfinishedVideoTask()) { logStore.addLog(`当前视频任务未完成,先完成视频,再进入章节测验`, "info"); setTimeout(() => monitorIframes(), 2000); return true; } const clickedQuizTab = await findAndClickChapterQuizTab(); if (clickedQuizTab) { logStore.addLog(`等待章节测验内容加载...`, "info"); const quizLoaded = await waitForQuizPage(12000); if (quizLoaded) { logStore.addLog(`章节测验页面加载成功,开始自动答题`, "success"); await autoAnswerQuiz(); await submitQuiz(); } else { logStore.addLog(`章节测验未检测到题目,回到任务点扫描`, "warning"); setTimeout(() => monitorIframes(), 1000); } } else { logStore.addLog(`未找到章节测验标签,按当前章节任务点继续扫描`, "warning"); setTimeout(() => monitorIframes(), 1000); } return true; } catch(e) { logStore.addLog(`章节调度失败,回退通用入口扫描: ${e.message}`, "warning"); return false; } }; const findAndClickChapterQuizTab = async () => { const docs = [document]; for (const iframe of document.querySelectorAll('iframe')) { try { const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document; if (iframeDoc) docs.push(iframeDoc); } catch(e) {} } for (const doc of docs) { const tab = findChapterQuizTabInDoc(doc); if (!tab) continue; try { if (tab.tagName === 'OPTION') { tab.selected = true; const select = tab.closest('select'); if (select) select.dispatchEvent(new Event('change', { bubbles: true })); tab.click(); } else { safeClick(tab); } logStore.addLog(`已点击章节测验标签`, "success"); await new Promise(resolve => setTimeout(resolve, 2500)); return true; } catch(e) { logStore.addLog(`点击章节测验标签失败: ${e.message}`, "warning"); } } return false; }; await new Promise(resolve => setTimeout(resolve, 1500)); if (thisTaskId !== _globalTaskId) { return; } logStore.addLog(`等待页面加载完成,开始扫描任务点`, "info"); FrameScanner.collectDeep(documentElement).subscribe((allIframes) => { _currentWatchSubscription = rxjs.from(allIframes).pipe(concatMap((iframe) => handleSingleFrame(iframe))).subscribe({ complete: async () => { try { if (thisTaskId === _globalTaskId) { // 智能等待作业 iframe 加载:最多等待 8 秒,每 1 秒检测一次(参考jinmu.js轮询模式) let hasUnansweredAssignments = false; const maxWaitTime = 8000; const checkInterval = 1000; let waitedTime = 0; while (waitedTime < maxWaitTime) { await new Promise(resolve => setTimeout(resolve, checkInterval)); waitedTime += checkInterval; if (thisTaskId !== _globalTaskId) { return; } // 每次检测前重新扫描 iframe(可能有新的动态加载) hasUnansweredAssignments = await checkUnansweredAssignments(documentElement); if (hasUnansweredAssignments) { logStore.addLog(`第 ${Math.floor(waitedTime / checkInterval)} 次检测发现未完成任务`, "warning"); break; } } if (thisTaskId !== _globalTaskId) { return; } if (!hasUnansweredAssignments) { logStore.addLog(`未检测到未完成的答题任务`, "info"); } if(hasUnansweredAssignments){ // === 【修复】先检查是否已经在答题页面 === const alreadyOnAnswerPage = document.querySelector('.TiMu, .questionLi, .subject_node, [class*="TiMu"], .examPaper_subject, .answerArea') !== null; if(alreadyOnAnswerPage){ logStore.addLog(`已在答题页面,直接开始答题`, "info"); // 直接触发答题 try{ if(typeof autoAnswerOnce === 'function') setTimeout(autoAnswerOnce, 500); }catch(e){} try{ if(typeof startAutoLoop === 'function') setTimeout(startAutoLoop, 500); }catch(e){} return; } logStore.addLog(`检测到未完成的作业/测验,准备跳转答题`, "warning"); const cxDispatched = await tryCxStudyDispatcher(); if (cxDispatched) { return; } logStore.addLog(`正在定位答题入口...`, "info"); // 等待页面稳定 await quickRandomDelay(); // 增强版答题入口选择器 - 覆盖学习通各种DOM结构 // 【修复】优先使用 .jobUnfinishCount 检测未完成任务的章节 const answerEntrySelectors = [ // === 第一优先级:有未完成任务的章节项(最准确) === '.posCatalog_select:has(.jobUnfinishCount)', '.posCatalog_select:has(input.jobUnfinishCount)', // === 第二优先级:学习通标准答题入口 === '.ans-job-icon:not(.ans-job-icon-clear)', '.ans-job-icon:not(.ans-job-finished)', '.jobclass:not(.finished)', // === 第三优先级:作业/测验链接 === 'a[href*="workId"]', 'a[href*="examId"]', 'a[href*="testId"]', 'a[href*="ans-quiz"]', 'a[href*="zy"]', 'a[href*="ks"]', '.work-icon', '.exam-icon', '.assignment-icon', // === 第四优先级:学习通实际DOM结构 === '.ans-job', '.ans-job-title', '.ans-job-content', '.ans-job-btn', '[class*="ans-job"]', // === 第五优先级:章节列表项(带任务标记) === '.posCatalog_select', '.posCatalog_item', '.chapterItem', '.chapter_item', '.section_item', // === 第六优先级:作业/测验容器 === '.workList', '.work_list', '[class*="workList"]', '.examList', '.exam_list', '[class*="examList"]', '.testList', '.test_list', '[class*="testList"]', // === 第七优先级:按钮类型 === '.btn_work', '.btn_exam', '.btn_test', '.btn_answer', 'button[class*="job"]', 'button[class*="work"]', 'button[class*="exam"]', 'button[class*="test"]', // === 第八优先级:iframe中的答题入口 === 'iframe[src*="work"]', 'iframe[src*="exam"]', 'iframe[src*="test"]', 'iframe[src*="quiz"]', 'iframe[src*="ans-quiz"]', // === 第九优先级:移动端适配选择器 === '[data-type="quiz"]', '[data-type="test"]', '[data-type="exam"]', '[data-type="homework"]', '[data-type="work"]', '[data-action="quiz"]', '[data-action="test"]', '[data-action="exam"]' ]; let answerEntry = null; let foundSelector = ''; // 第一轮:查找可见的答题入口 for (const selector of answerEntrySelectors) { try { const el = document.querySelector(selector); if (el && el.offsetParent !== null) { const rect = el.getBoundingClientRect(); if (rect.width > 0 && rect.height > 0) { answerEntry = el; foundSelector = selector; logStore.addLog(`找到答题入口: ${selector}`, "success"); break; } } } catch (e) { // 忽略无效选择器 } } // 第二轮:如果没找到,尝试查找隐藏的答题入口(可能在iframe中) if (!answerEntry) { logStore.addLog(`未在主页找到答题入口,尝试在iframe中查找...`, "info"); const iframes = document.querySelectorAll('iframe'); for (const iframe of iframes) { try { const iframeDoc = iframe.contentDocument || iframe.contentWindow.document; if (iframeDoc) { for (const selector of answerEntrySelectors) { try { const el = iframeDoc.querySelector(selector); if (el) { answerEntry = el; foundSelector = `iframe:${selector}`; logStore.addLog(`在iframe中找到答题入口: ${foundSelector}`, "success"); break; } } catch (e) { // 跨域iframe无法访问 } } } } catch (e) { // 跨域iframe无法访问 } if (answerEntry) break; } } // 第三轮:如果还是没找到,尝试通过文本内容查找 if (!answerEntry) { logStore.addLog(`尝试通过文本内容查找答题入口...`, "info"); const allElements = document.querySelectorAll('*'); for (const el of allElements) { const text = el.textContent || ''; if (/作业|测验|考试|答题|work|exam|test|quiz/.test(text) && el.offsetParent !== null && el.getBoundingClientRect().width > 0 && el.getBoundingClientRect().height > 0) { // 优先选择按钮或链接 if (el.tagName === 'BUTTON' || el.tagName === 'A' || el.classList.contains('ans-job')) { answerEntry = el; foundSelector = `text:${text.substring(0, 20)}...`; logStore.addLog(`通过文本找到答题入口: ${foundSelector}`, "success"); break; } } } } // 第四轮:如果还是没找到,尝试通过XPath查找 if (!answerEntry) { logStore.addLog(`尝试通过XPath查找答题入口...`, "info"); const xpathExpressions = [ '//a[contains(text(), "作业")]', '//a[contains(text(), "测验")]', '//a[contains(text(), "考试")]', '//a[contains(text(), "答题")]', '//button[contains(text(), "作业")]', '//button[contains(text(), "测验")]', '//button[contains(text(), "考试")]', '//button[contains(text(), "答题")]', '//*[contains(@class, "job")]', '//*[contains(@class, "work")]', '//*[contains(@class, "exam")]', '//*[contains(@class, "test")]', '//*[contains(@class, "quiz")]', '//*[contains(@class, "assignment")]' ]; for (const xpath of xpathExpressions) { try { const result = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); const el = result.singleNodeValue; if (el && el.offsetParent !== null) { const rect = el.getBoundingClientRect(); if (rect.width > 0 && rect.height > 0) { answerEntry = el; foundSelector = `xpath:${xpath}`; logStore.addLog(`通过XPath找到答题入口: ${foundSelector}`, "success"); break; } } } catch (e) { // 忽略无效XPath } } } // 第五轮:如果还是没找到,尝试通过URL参数查找 if (!answerEntry) { logStore.addLog(`尝试通过URL参数查找答题入口...`, "info"); const currentUrl = window.location.href; const urlParams = new URLSearchParams(currentUrl.split('?')[1]); // 检查是否有课程ID、章节ID等参数 const courseId = urlParams.get('courseId') || urlParams.get('classId'); const chapterId = urlParams.get('chapterId') || urlParams.get('sectionId'); if (courseId || chapterId) { logStore.addLog(`检测到课程参数,尝试构造答题URL...`, "info"); // 尝试构造答题URL并跳转 const answerUrl = currentUrl.includes('work') || currentUrl.includes('exam') || currentUrl.includes('test') ? currentUrl : `${currentUrl.split('?')[0]}?courseId=${courseId}&chapterId=${chapterId}&type=work`; logStore.addLog(`尝试跳转到答题页面: ${answerUrl}`, "info"); window.location.href = answerUrl; return; } } if (answerEntry) { logStore.addLog(`找到答题入口,正在跳转`, "success"); await quickRandomDelay(); // 策略1:如果是 posCatalog_select(章节列表项),先切换章节再重新扫描任务点 if (answerEntry.classList.contains('posCatalog_select') || answerEntry.closest('.posCatalog_select')) { const chapterItem = answerEntry.classList.contains('posCatalog_select') ? answerEntry : answerEntry.closest('.posCatalog_select'); const nameEl = chapterItem.querySelector('.posCatalog_name'); // 步骤1:调用 getTeacherAjax 切换到目标章节(加载视频页面) if (nameEl) { const onclick = nameEl.getAttribute('onclick'); if (onclick && onclick.includes('getTeacherAjax')) { const match = onclick.match(/getTeacherAjax\(['"](.+?)['"],\s*['"](.+?)['"],\s*['"](.+?)['"]\)/); if (match) { const [, courseId, classId, chapterId] = match; logStore.addLog(`调用 getTeacherAjax 切换章节(${courseId}, ${classId}, ${chapterId})`, "info"); try { const targetWindow = (typeof unsafeWindow !== 'undefined') ? unsafeWindow : window; if (typeof targetWindow.getTeacherAjax === 'function') { targetWindow.getTeacherAjax(courseId, classId, chapterId); } else { nameEl.click(); } } catch(e) { logStore.addLog(`getTeacherAjax调用失败: ${e.message},尝试点击`, "warning"); nameEl.click(); } } else { nameEl.click(); } } else { nameEl.click(); } } else { safeClick(answerEntry); } logStore.addLog(`等待章节切换完成...`, "info"); await new Promise(resolve => setTimeout(resolve, 3000)); if (thisTaskId !== _globalTaskId) { return; } const activeChapter = document.querySelector(`.posCatalog_active[id="cur${chapterItem.id?.replace(/^cur/, '') || ''}"]`) || document.querySelector('.posCatalog_active'); if (activeChapter) { try { activeChapter.scrollIntoView({ behavior: "smooth", block: "center" }); } catch(e) {} } logStore.addLog(`已切换到未完成章节,尝试进入章节测验`, "info"); const clickedQuizTab = await findAndClickChapterQuizTab(); if (clickedQuizTab) { logStore.addLog(`等待章节测验内容加载...`, "info"); const quizLoaded = await waitForQuizPage(12000); if (quizLoaded) { logStore.addLog(`章节测验页面加载成功,开始自动答题`, "success"); await autoAnswerQuiz(); await submitQuiz(); } else { logStore.addLog(`章节测验标签已点击,但未检测到题目内容,重新扫描任务点`, "warning"); setTimeout(() => monitorIframes(), 1000); } } else { logStore.addLog(`未找到章节测验标签,当前可能是视频任务,继续处理当前任务点`, "warning"); setTimeout(() => monitorIframes(), 1000); } return; } // 策略2:如果是标签且有href,直接跳转URL const href = answerEntry.href || answerEntry.getAttribute('href'); if (href && href !== '#' && href !== 'javascript:void(0)' && href !== 'javascript:;') { const fullUrl = href.startsWith('http') ? href : `${window.location.origin}${href}`; logStore.addLog(`直接跳转到答题URL: ${fullUrl}`, "info"); window.location.href = fullUrl; return; } // 策略3:如果有onclick属性,直接执行 const onclick = answerEntry.getAttribute('onclick'); if (onclick) { logStore.addLog(`通过onclick触发跳转: ${onclick.substring(0, 60)}...`, "info"); try { eval(onclick); logStore.addLog(`等待答题页面加载...`, "info"); const quizLoaded = await waitForQuizPage(15000); if (quizLoaded) { logStore.addLog(`答题页面加载成功,开始自动答题`, "success"); await autoAnswerQuiz(); await submitQuiz(); } else { logStore.addLog(`答题页面加载超时,尝试兜底跳转...`, "warning"); await fallbackToQuizUrl(); } return; } catch(e) { logStore.addLog(`onclick执行失败: ${e.message},尝试点击`, "warning"); } } // 策略4:点击元素 safeClick(answerEntry); // 等待答题页面加载 logStore.addLog(`等待答题页面加载...`, "info"); const quizLoaded = await waitForQuizPage(15000); if (quizLoaded) { logStore.addLog(`答题页面加载成功,开始自动答题`, "success"); await autoAnswerQuiz(); await submitQuiz(); } else { logStore.addLog(`答题页面加载超时,尝试兜底跳转...`, "warning"); await fallbackToQuizUrl(); } return; } else { logStore.addLog(`未找到答题入口,尝试直接跳转...`, "warning"); // 最后的兜底策略:尝试直接跳转到常见的答题URL await fallbackToQuizUrl(); } return; } const autoSwitch = configStore.platformParams?.cx?.parts?.[2]?.params?.[1]?.value; logStore.addLog(`本页任务点已全部完成,${autoSwitch ? "正前往下一章节" : "自动切换已关闭"}`, "success"); if (autoSwitch) { // 关键修复:在跳转下一章节前,再次检测是否有未完成的答题入口 logStore.addLog(`跳转前检测:是否有未完成的答题入口...`, "info"); const hasUnansweredBeforeNext = await checkUnansweredAssignments(documentElement); if (hasUnansweredBeforeNext) { logStore.addLog(`检测到未完成的答题入口,优先跳转答题而非下一章节`, "warning"); logStore.addLog(`正在定位答题入口...`, "info"); await quickRandomDelay(); // 使用与之前相同的答题入口选择器(增强版) const answerEntrySelectors = [ '.ans-job-icon', '.jobclass', '[class*="job"]', 'a[href*="work"]', 'a[href*="exam"]', 'a[href*="answer"]', '.work-icon', '.exam-icon', '.assignment-icon', '.ans-job', '.ans-job-title', '.ans-job-content', '.ans-job-btn', '[class*="ans-job"]', '[class*="work"]', '[class*="exam"]', '[class*="test"]', '[class*="quiz"]', '[class*="assignment"]', 'button[class*="job"]', 'button[class*="work"]', 'button[class*="exam"]', 'button[class*="test"]', 'button[class*="quiz"]', 'a[class*="job"]', 'a[class*="work"]', 'a[class*="exam"]', 'a[class*="test"]', 'a[class*="quiz"]', '[class*="作业"]', '[class*="测验"]', '[class*="考试"]', '[class*="答题"]', 'iframe[src*="work"]', 'iframe[src*="exam"]', 'iframe[src*="test"]', 'iframe[src*="quiz"]', 'iframe[src*="answer"]', '.chapterItem', '.chapter_item', '[class*="chapter"]', '.course_section', '.section_item', '[class*="section"]', '.taskitem', '.task_item', '[class*="task"]', '.workList', '.work_list', '[class*="workList"]', '.examList', '.exam_list', '[class*="examList"]', '.testList', '.test_list', '[class*="testList"]', '.btn_work', '.btn_exam', '.btn_test', '.btn_answer', '[class*="btn_work"]', '[class*="btn_exam"]', '[class*="btn_test"]', '[class*="btn_answer"]', 'a[href*="chapter"]', 'a[href*="section"]', 'a[href*="task"]', 'a[href*="course"]', '.chapterDiv', '.chapter_div', '[class*="chapterDiv"]', '.sectionDiv', '.section_div', '[class*="sectionDiv"]', '.icon_work', '.icon_exam', '.icon_test', '.icon_answer', '[class*="icon_work"]', '[class*="icon_exam"]', '[class*="icon_test"]', '[class*="icon_answer"]', // ========== 新增:学习通章节列表特有选择器 ========== // 章节列表项 '.posCatalog_select', '.posCatalog_item', '.posCatalog li', '.lesson_item', '.lesson-item', '.lessonItem', // 章节测验标记(黄色/橙色数字标记) '.orangeIcon', '.icon_yellow', '.icon_orange', '.catalogNum', '.catalog-num', '.chapterNum', '.chapter-num', '.sectionNum', '.section-num', '.lessonNum', '.lesson-num', '.numIcon', '.iconNum', '.num-icon', '.icon-num', '.badge-num', '.num-badge', // 章节测验入口(包含数字标记的章节项) '.posCatalog_select:has(.orangeIcon)', '.posCatalog_select:has(.icon_yellow)', '.posCatalog_select:has([class*="num"])', '.posCatalog_item:has(.orangeIcon)', '.posCatalog_item:has(.icon_yellow)', '.chapterItem:has(.orangeIcon)', '.chapter-item:has(.orangeIcon)', '.sectionItem:has(.orangeIcon)', '.section-item:has(.orangeIcon)', // 学习通移动端适配选择器 '[data-type="quiz"]', '[data-type="test"]', '[data-type="exam"]', '[data-type="homework"]', '[data-type="work"]', '[data-action="quiz"]', '[data-action="test"]', '[data-action="exam"]', '[data-action="homework"]', '[data-action="work"]' ]; let answerEntry = null; let foundSelector = ''; for (const selector of answerEntrySelectors) { try { const el = document.querySelector(selector); if (el && el.offsetParent !== null) { const rect = el.getBoundingClientRect(); if (rect.width > 0 && rect.height > 0) { answerEntry = el; foundSelector = selector; logStore.addLog(`找到答题入口: ${selector}`, "success"); break; } } } catch (e) { // 忽略无效选择器 } } if (answerEntry) { logStore.addLog(`找到答题入口,正在跳转`, "success"); await quickRandomDelay(); safeClick(answerEntry); // 等待答题页面加载 logStore.addLog(`等待答题页面加载...`, "info"); const quizLoaded = await waitForQuizPage(15000); if (quizLoaded) { logStore.addLog(`答题页面加载成功,开始自动答题`, "success"); await autoAnswerQuiz(); await submitQuiz(); } else { logStore.addLog(`答题页面加载超时,回退到下一章节跳转`, "warning"); } return; } else { logStore.addLog(`未找到答题入口,尝试直接跳转...`, "warning"); const currentUrl = window.location.href; const baseUrl = currentUrl.split('?')[0]; const possibleUrls = [ `${baseUrl}?type=work`, `${baseUrl}?type=exam`, `${baseUrl}?type=test`, `${baseUrl}/work`, `${baseUrl}/exam`, `${baseUrl}/test` ]; for (const url of possibleUrls) { try { logStore.addLog(`尝试跳转到: ${url}`, "info"); window.location.href = url; return; } catch (e) { // 忽略跳转失败 } } logStore.addLog(`所有跳转尝试失败,回退到下一章节跳转`, "warning"); } } else { // 如果没有未完成的答题入口,继续执行下一章节跳转 logStore.addLog(`没有未完成的答题入口,执行下一章节跳转`, "info"); } } // 关闭 if(hasUnansweredAssignments) // 关键修复:在跳转下一章节前,检测是否有未完成的视频 const hasUnfinishedVideo = hasVideoOrAudio(); if (hasUnfinishedVideo) { logStore.addLog(`检测到未完成的视频,停止跳转并继续播放视频`, "warning"); return; } // 快速随机延迟 await quickRandomDelay(); const nextBtn1 = documentElement.querySelector("#prevNextFocusNext"); const nextBtn2 = document.querySelector(".jb_btn.jb_btn_92.fr.fs14.nextChapter"); const nextBtn3 = document.querySelector("#nextBtn"); const nextBtn4 = document.querySelector(".nextChapter"); let targetBtn = null; if (nextBtn1 && nextBtn1.style.display !== "none") { targetBtn = nextBtn2 || nextBtn1; } else if (nextBtn2 && nextBtn2.style.display !== "none") { targetBtn = nextBtn2; } else if (nextBtn3 && nextBtn3.style.display !== "none") { targetBtn = nextBtn3; } else if (nextBtn4 && nextBtn4.style.display !== "none") { targetBtn = nextBtn4; } if (!targetBtn) { logStore.addLog("未找到下一章节按钮,停止自动切换", "warning"); } else { _justClickedNext = true; await quickRandomDelay(); if (thisTaskId !== _globalTaskId) { return; } logStore.addLog("正在前往下一章节...", "primary"); safeClick(targetBtn); } } } catch (e) { logStore.addLog(`扫描任务点异常: ${e.message}`, "danger"); console.error('[扫描任务点] 异常:', e); } } }); }); }; const calculateEnc = (classId, uid, jobId, objectId, playTime, duration) => { const str = `[${classId}][${uid}][${jobId}][${objectId}][${playTime * 1000}][d_yHJ!$pdA~5][${duration * 1000}][0_${duration}]`; return md5(str); }; const formatDuration = (seconds) => { const mins = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; }; // 增强版视频检测函数 - 检查视频是否未完成播放 const hasVideoOrAudio = () => { try { const allIframes = FrameScanner.collectDeepSync(document.documentElement); for (const iframe of allIframes) { const src = iframe.src || ''; if (src.includes('video') || src.includes('audio')) { const parent = iframe.parentElement; const ansJobIcon = parent?.querySelector('.ans-job-icon'); if (ansJobIcon) { // 检查视频任务点是否已完成 const ariaLabel = ansJobIcon.getAttribute('aria-label') || ''; const titleAttr = ansJobIcon.getAttribute('title') || ''; const isCompleted = ariaLabel.includes('已完成') || titleAttr.includes('已完成') || ariaLabel.includes('100%') || titleAttr.includes('100%'); // 如果视频任务点未完成,则返回true表示有未完成的视频 if (!isCompleted) { return true; } } // 尝试直接检查iframe内的视频播放状态 try { const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document; if (iframeDoc) { const video = iframeDoc.querySelector('video'); if (video) { // 检查视频是否未播放完成(播放进度 < 90% 或视频时长 > 0且未结束) const progress = video.duration > 0 ? (video.currentTime / video.duration) * 100 : 0; if (!video.ended && progress < 90) { return true; } } } } catch (e) { // 跨域无法访问,假设视频未完成 return true; } } } return false; } catch (e) { console.log('[检测视频] 检测失败:', e); return false; } }; const completedSimulatedIds = new Set(); const processingSimulatedId = { current: null }; const clearOverlayAndBanner = () => { if (window._blockPlayInterval) { clearInterval(window._blockPlayInterval); window._blockPlayInterval = null; } if (window._blockOverlay) { window._blockOverlay.forEach(o => { try { if (document.contains(o)) o.remove(); } catch(e) {} }); window._blockOverlay = []; } const banner = document.getElementById('_simulate_banner_'); if (banner) banner.remove(); if (window._blockPlayScroll) { window.removeEventListener('scroll', window._blockPlayScroll, true); window.removeEventListener('resize', window._blockPlayScroll); window._blockPlayScroll = null; } }; const showOverlayAndBanner = () => { clearOverlayAndBanner(); if (!window._blockOverlay) window._blockOverlay = []; const getVideoIframes = () => { try { const allIframes = FrameScanner.collectDeepSync(document.documentElement); return allIframes.filter(fr => { const src = fr.src || ''; return src.includes('video') || src.includes('audio'); }); } catch (e) { return []; } }; const createOverlay = () => { try { window._blockOverlay.forEach(o => { try { o.remove(); } catch(e) {} }); window._blockOverlay = []; const videoIframes = getVideoIframes(); for (const iframe of videoIframes) { const rect = iframe.getBoundingClientRect(); if (rect.width > 50 && rect.height > 50) { const overlay = document.createElement('div'); overlay.className = '_simulate_block_overlay_'; overlay.style.cssText = `position:fixed;top:${rect.top}px;left:${rect.left}px;width:${rect.width}px;height:${rect.height}px;z-index:2147483646;cursor:not-allowed;background:rgba(0,0,0,0.25);border-radius:8px;pointer-events:none;`; const ownerDoc = iframe.ownerDocument; if (ownerDoc && ownerDoc.body && ownerDoc.body !== document.body) { ownerDoc.body.appendChild(overlay); } else { document.body.appendChild(overlay); } window._blockOverlay.push(overlay); } } } catch (e) {} }; const updateOverlayPositions = () => {}; createOverlay(); window._blockPlayScroll = updateOverlayPositions; window.addEventListener('scroll', updateOverlayPositions, true); window.addEventListener('resize', updateOverlayPositions); window._blockPlayInterval = setInterval(createOverlay, 1000); if (!document.getElementById('_simulate_banner_')) { const banner = document.createElement('div'); banner.id = '_simulate_banner_'; banner.style.cssText = 'position:fixed;bottom:20px;left:50%;transform:translateX(-50%);z-index:100002;background:var(--jb-primary);color:#fff;padding:12px 28px;border-radius:30px;font-size:15px;font-weight:600;text-align:center;box-shadow:0 4px 20px rgba(var(--jb-primary-rgb),0.4);cursor:not-allowed;white-space:nowrap;pointer-events:none;'; banner.innerHTML = '🎬 模拟播放模式下无需播放视频,播放进度可在主页信息查看'; document.body.appendChild(banner); } }; const stopSimulatePlayIfNeeded = () => { if (window._simulateActive) { const hasMedia = hasVideoOrAudio(); if (!hasMedia) { window._simulateActive = false; logStore.addLog('检测到视频/音频已消失,停止模拟播放', 'warning'); if (window._currentMediaInterval) { clearInterval(window._currentMediaInterval); window._currentMediaInterval = null; } if (window._simulateLoopId) { BackgroundWorker.stop(window._simulateLoopId); window._simulateLoopId = null; } progressStore.update({ isPlaying: false }); processingSimulatedId.current = null; simulateVideoPlay._currentObjectId = null; clearOverlayAndBanner(); } } }; const forceStopSimulatePlay = () => { if (window._simulateActive) { window._simulateActive = false; if (window._currentMediaInterval) { clearInterval(window._currentMediaInterval); window._currentMediaInterval = null; } if (window._simulateLoopId) { BackgroundWorker.stop(window._simulateLoopId); window._simulateLoopId = null; } progressStore.update({ isPlaying: false }); processingSimulatedId.current = null; simulateVideoPlay._currentObjectId = null; clearOverlayAndBanner(); } }; const getVideoInfo = async (objectId) => { return new Promise((resolve, reject) => { const host = window.location.host; const protocol = window.location.protocol; const FID = _unsafeWindow.FID || ''; const statusUrl = `${protocol}//${host}/ananas/status/${objectId}?k=${FID}&flag=normal&_dc=${Date.now()}`; let vrefer = ''; try { const videoIframe = _unsafeWindow.document.querySelector('.ans-attach-online.ans-insertvideo-online'); if (videoIframe) { vrefer = videoIframe.src; } } catch (e) {} if (!vrefer) { vrefer = `${protocol}//${host}/ananas/modules/video/index.html?v=2022-1118-1729`; } _GM_xmlhttpRequest({ method: "get", url: statusUrl, headers: { 'Host': host, 'Referer': vrefer, 'Sec-Fetch-Site': 'same-origin' }, onload: function(res) { try { if (res.status === 200) { const data = JSON.parse(res.responseText); resolve(data); } else { reject(new Error(`HTTP ${res.status}`)); } } catch (e) { reject(e); } }, onerror: function(err) { reject(err); } }); }); }; const getTaskParams = () => { try { const topWindow = _unsafeWindow.top; if (topWindow.margs) { return topWindow.margs; } const urlParams = new URLSearchParams(window.location.search); return { clazzId: urlParams.get('clazzId') || urlParams.get('classId'), courseId: urlParams.get('courseId'), knowledgeid: urlParams.get('knowledgeid') || urlParams.get('chapterId') }; } catch (e) { console.error('[模拟播放] 获取任务参数失败:', e); return null; } }; const getEvasionRate = (baseRate) => { const evasionRate = baseRate * (0.95 + Math.random() * 0.1); return Math.max(0.5, Math.min(baseRate * 1.05, evasionRate)); }; let smartSpeedState = { currentSpeed: 0, lastChangeTime: 0, changeInterval: 30000 }; const getSmartSpeed = (baseRate) => { const now = Date.now(); if (now - smartSpeedState.lastChangeTime > smartSpeedState.changeInterval) { const variation = (Math.random() - 0.5) * baseRate * 0.1; smartSpeedState.currentSpeed = Math.max(0.8, Math.min(baseRate * 1.05, baseRate + variation)); smartSpeedState.lastChangeTime = now; } return smartSpeedState.currentSpeed; }; let behaviorState = { lastMouseMove: 0 }; const simulateUserBehavior = () => { const now = Date.now(); if (now - behaviorState.lastMouseMove > 10000 + Math.random() * 20000) { const x = Math.random() * window.innerWidth; const y = Math.random() * window.innerHeight; document.dispatchEvent(new MouseEvent('mousemove', { clientX: x, clientY: y, bubbles: true })); behaviorState.lastMouseMove = now; } }; const compensateDuration = (playTime, duration, baseRate) => playTime; const simulateVideoPlay = async (iframe, iframeDocument, mediaType) => { return new Promise(async (resolve) => { const releaseLock = () => { if (processingSimulatedId.current === (simulateVideoPlay._currentObjectId)) { processingSimulatedId.current = null; simulateVideoPlay._currentObjectId = null; } }; const safeResolve = (val) => { releaseLock(); resolve(val); }; logStore.addLog(`发现一个${mediaType},当前播放模式: 模拟播放`, "primary"); try { const mediaElement = iframeDocument?.documentElement?.querySelector(mediaType); if (mediaElement) { mediaElement.pause(); mediaElement.muted = true; mediaElement.autoplay = false; } } catch (e) { console.log('[模拟播放] 暂停视频失败:', e); } try { let objectId = null; let jobId = null; let otherInfo = ''; let reportUrl = ''; let classId = null; let uid = null; let duration = null; let dtoken = null; let iframeSrc = iframe.src || ''; let videoName = mediaType === 'video' ? '视频' : '音频'; try { let prevTitleElement = null; let parent = iframe.parentElement; while (parent && !prevTitleElement) { prevTitleElement = parent.querySelector('.prev_title'); parent = parent.parentElement; if (parent === document.body || parent === document.documentElement) break; } if (!prevTitleElement) { prevTitleElement = document.querySelector('.prev_title'); } if (prevTitleElement) { const titleText = prevTitleElement.innerText || prevTitleElement.textContent || ''; videoName = titleText.replace(/【上】|【下】|【上$|【下$/g, '').trim(); if (videoName && videoName !== (mediaType === 'video' ? '视频' : '音频')) { logStore.addLog(`获取到视频名称: ${videoName}`, "primary"); } else { logStore.addLog(`从.prev_title获取到的文本: "${titleText}"`, "warning"); } } else { logStore.addLog(`未找到.prev_title元素`, "warning"); } } catch (e) { logStore.addLog(`从标题获取失败: ${e.message}`, "danger"); } const getStr = (str, start, end) => { const startIndex = str.indexOf(start); if (startIndex === -1) return null; const content = str.substring(startIndex + start.length); const endIndex = content.indexOf(end); if (endIndex === -1) return null; return content.substring(0, endIndex); }; let pageData = null; try { if (_unsafeWindow.param) { try { const parsed = JSON.parse(_unsafeWindow.param); if (parsed?.attachments) pageData = parsed; } catch (e) {} } if (!pageData && _unsafeWindow.mArg?.attachments) { pageData = _unsafeWindow.mArg; } } catch (e) {} if (!pageData) { try { const allIframes = FrameScanner.collectDeepSync(document.documentElement); for (const ifr of allIframes) { try { if (!ifr.contentWindow) continue; const win = ifr.contentWindow; if (win.param) { try { const parsed = JSON.parse(win.param); if (parsed?.attachments) { pageData = parsed; break; } } catch (e) {} } if (!pageData && win.mArg?.attachments) { pageData = win.mArg; break; } } catch (e) {} } } catch (e) {} } const documentsToTry = []; if (iframeDocument) { documentsToTry.push({ name: 'iframeDocument', doc: iframeDocument }); } documentsToTry.push({ name: 'currentWindow', doc: _unsafeWindow.document }); try { if (_unsafeWindow.top && _unsafeWindow.top.document) { documentsToTry.push({ name: 'topWindow', doc: _unsafeWindow.top.document }); } } catch (e) {} for (const { name, doc } of documentsToTry) { if (pageData) break; try { const scripts = doc.getElementsByTagName('script'); for (let i = 0; i < scripts.length; i++) { const scriptContent = scripts[i].innerHTML; if (scriptContent.indexOf('mArg = "";') !== -1 && scriptContent.indexOf('==UserScript==') === -1) { const param = getStr(scriptContent, 'try{\n mArg = ', ';\n}catch(e){'); if (param) { pageData = JSON.parse(param); break; } } if (!pageData && scriptContent.indexOf('mArg=') !== -1 && scriptContent.indexOf('==UserScript==') === -1) { const match = scriptContent.match(/mArg\s*=\s*(\{[\s\S]*?\});/); if (match) { try { pageData = JSON.parse(match[1]); break; } catch (e) {} } } } if (!pageData && name === 'topWindow') { for (let i = 0; i < scripts.length; i++) { const content = scripts[i].innerHTML; if (content.indexOf('clazzId') !== -1 && content.length > 10000) { const attachmentsMatch = content.match(/["']attachments["']\s*:\s*(\[[\s\S]*?\])/); if (attachmentsMatch) { try { const jsonStr = `{"attachments": ${attachmentsMatch[1]}}`; const parsed = JSON.parse(jsonStr); if (parsed.attachments && parsed.attachments.length > 0) { pageData = { attachments: parsed.attachments }; break; } } catch (e) { logStore.addLog(`[${name}] 解析attachments失败: ${e.message}`, "warning"); } } const clazzIdMatch = content.match(/stu_clazzId\s*=\s*["'](\d+)["']/); const courseIdMatch = content.match(/stu_CourseId\s*=\s*["'](\d+)["']/); const knowledgeIdMatch = content.match(/stu_knowledgeId\s*=\s*["'](\d+)["']/); if (clazzIdMatch || courseIdMatch) { if (!pageData) pageData = {}; pageData.defaults = { clazzId: clazzIdMatch ? clazzIdMatch[1] : null, courseId: courseIdMatch ? courseIdMatch[1] : null, knowledgeId: knowledgeIdMatch ? knowledgeIdMatch[1] : null }; } } if (!pageData && content.indexOf('"attachments"') !== -1) { const jsonMatch = content.match(/\{[\s\S]*?"attachments"[\s\S]*?\}/); if (jsonMatch) { try { pageData = JSON.parse(jsonMatch[0]); break; } catch (e) {} } } } } } catch (e) { logStore.addLog(`[${name}] 获取失败: ${e.message}`, "warning"); } } if (!pageData || (pageData.defaults && !pageData.defaults.reportUrl)) { try { const windowsToTry = [_unsafeWindow]; try { if (_unsafeWindow.top) windowsToTry.push(_unsafeWindow.top); } catch (e) {} try { if (_unsafeWindow.parent) windowsToTry.push(_unsafeWindow.parent); } catch (e) {} for (const ifr of FrameScanner.collectDeepSync(document.documentElement)) { try { if (ifr.contentWindow) windowsToTry.push(ifr.contentWindow); } catch (e) {} } for (const win of windowsToTry) { const props = ['margs', 'mArg', 'pageData', 'courseData']; for (const prop of props) { try { const val = win[prop]; if (val && typeof val === 'object') { if (val.attachments) { pageData = val; logStore.addLog(`从window.${prop}获取完整数据成功(含attachments)`, "success"); break; } if (!pageData && val.defaults && val.defaults.reportUrl) { pageData = val; logStore.addLog(`从window.${prop}获取数据成功(含reportUrl)`, "success"); break; } } } catch (e) {} } if (pageData && pageData.defaults && pageData.defaults.reportUrl) break; } } catch (e) { logStore.addLog(`获取window数据失败: ${e.message}`, "warning"); } } if (!pageData) { logStore.addLog(`无法获取mArg数据,回退到普通播放模式`, "warning"); } if (pageData) { if (pageData.defaults) { classId = pageData.defaults.clazzId; uid = pageData.defaults.userid || getUid(); reportUrl = pageData.defaults.reportUrl || ''; } if (pageData.attachments && pageData.attachments.length > 0) { const iframeSrc = iframe.src; let srcObjectId = null; const srcMatch = iframeSrc.match(REGEX.OBJECT_ID); if (srcMatch) { srcObjectId = srcMatch[1]; } if (!srcObjectId) { srcObjectId = iframe.getAttribute('objectid') || iframe.getAttribute('data-objectid') || null; } for (const attachment of pageData.attachments) { if (attachment.isPassed === true) continue; if (completedSimulatedIds.has(attachment.property?.objectid)) continue; const moduleType = attachment.property?.module || ''; const isVideo = moduleType === 'video' || moduleType === 'insertvideo' || attachment.type === 'video'; const isAudio = moduleType === 'audio' || moduleType === 'insertaudio' || attachment.type === 'audio'; if ((mediaType === 'video' && isVideo) || (mediaType === 'audio' && isAudio)) { if (srcObjectId && attachment.property?.objectid === srcObjectId) { objectId = attachment.property.objectid; jobId = attachment.jobid; otherInfo = attachment.otherInfo || ''; videoName = attachment.property?.name || videoName; break; } if (!objectId && attachment.job === true) { objectId = attachment.property?.objectid; jobId = attachment.jobid; otherInfo = attachment.otherInfo || ''; videoName = attachment.property?.name || videoName; } } } if (!objectId) { for (const attachment of pageData.attachments) { if (attachment.isPassed === true) continue; if (completedSimulatedIds.has(attachment.property?.objectid)) continue; const moduleType = attachment.property?.module || ''; const isVideo = moduleType === 'video' || moduleType === 'insertvideo' || attachment.type === 'video'; const isAudio = moduleType === 'audio' || moduleType === 'insertaudio' || attachment.type === 'audio'; if ((mediaType === 'video' && isVideo) || (mediaType === 'audio' && isAudio)) { objectId = attachment.property?.objectid; jobId = attachment.jobid; otherInfo = attachment.otherInfo || ''; videoName = attachment.property?.name || videoName; break; } } } } } if (!objectId) { const iframeSrc = iframe.src || ''; const objectIdMatch = iframeSrc.match(REGEX.OBJECT_ID); objectId = objectIdMatch ? objectIdMatch[1] : null; if (!objectId) { const dataAttrs = ['data-objectid', 'data-object-id', 'objectid']; for (const attr of dataAttrs) { const val = iframe.getAttribute(attr); if (val) { objectId = val; break; } } } if (!objectId) { const nameOrId = iframe.name || iframe.id || ''; const nameMatch = nameOrId.match(/([a-f0-9]{24,})/i); if (nameMatch) { objectId = nameMatch[1]; } } } if (!objectId) { logStore.addLog(`无法获取objectId,回退到普通播放模式`, "danger"); return safeResolve(await playMediaDirectly(mediaType, iframeDocument)); } if (processingSimulatedId.current) { logStore.addLog(`该视频正在模拟播放中,跳过重复处理`, "warning"); return safeResolve(); } processingSimulatedId.current = objectId; simulateVideoPlay._currentObjectId = objectId; if (!dtoken || !duration) { const videoInfo = await getVideoInfo(objectId); duration = videoInfo.duration; dtoken = videoInfo.dtoken; } if (!duration || !dtoken) { logStore.addLog(`获取视频信息失败,回退到普通播放模式`, "danger"); return safeResolve(await playMediaDirectly(mediaType, iframeDocument)); } logStore.addLog(`视频时长: ${formatDuration(duration)}秒`, "primary"); if (!uid) { uid = cachedUid; } if (!classId) { const taskParams = getTaskParams(); classId = taskParams?.clazzId || taskParams?.classId; } if (!classId) { const pageUrl = window.location.href; const classIdMatch = pageUrl.match(/clazzId=(\d+)/) || pageUrl.match(/classId=(\d+)/); classId = classIdMatch ? classIdMatch[1] : null; } if (!jobId) { const jobIdAttrs = ['data-jobid', 'data-job-id', 'jobid', 'data-workid']; for (const attr of jobIdAttrs) { const val = iframe.getAttribute(attr); if (val) { jobId = val; break; } } if (!jobId) { const urlMatch = window.location.href.match(/[?&]jobid=([^&]+)/i); jobId = urlMatch ? urlMatch[1] : null; } if (!jobId && iframe.src) { const srcMatch = iframe.src.match(REGEX.JOB_ID); jobId = srcMatch ? srcMatch[1] : null; } if (!jobId) { jobId = objectId; } } if (!classId || !uid || !jobId) { logStore.addLog(`缺少必要参数: classId=${classId}, uid=${uid}, jobId=${jobId}`, "danger"); logStore.addLog(`回退到普通播放模式`, "warning"); return safeResolve(await playMediaDirectly(mediaType, iframeDocument)); } const autoMaxRate = configStore.platformParams.cx.parts[0].params[5].value || false; let playbackRate = configStore.platformParams.cx.parts[0].params[6].value || 1; const maxRate = getMaxPlaybackRate(iframeDocument, false); const speedDisabled = maxRate === 1; window.__maxPlaybackRate = maxRate; const playbackRateParam = configStore.platformParams.cx.parts[0].params[6]; playbackRateParam.max = autoMaxRate ? maxRate : 3; if (speedDisabled) { logStore.addLog(`⚠️ 此视频已被学习通禁用倍速,使用>1x倍速可能会导致学习进度被清空`, "warning"); } if (autoMaxRate) { playbackRate = maxRate; logStore.addLog(`自动倍速: ${playbackRate}x`, "success"); } else { const simulateMaxRate = 3; if (playbackRate > simulateMaxRate) { playbackRate = simulateMaxRate; logStore.addLog(`模拟播放倍速已调整为最大值: ${playbackRate}x`, "warning"); } } logStore.addLog(`播放倍速: ${playbackRate}x`, "primary"); const directComplete = configStore.platformParams.cx.parts[0].params[3].value || false; const host = window.location.host; const protocol = window.location.protocol; const videojs_id = String(parseInt(Math.random() * 9999999)); document.cookie = 'videojs_id=' + videojs_id + ';path=/'; const evasionEnabled = configStore.platformParams.cx.parts[0].params[1].value || false; const reportProgress = async (currentTime, isComplete) => { return new Promise((resolveReport) => { const enc = calculateEnc(classId, uid, jobId, objectId, currentTime, duration); if (enc.length !== 32) { logStore.addLog(`加密字符串计算失败`, "danger"); return resolveReport(false); } const currentIsdrag = isComplete ? '4' : (currentTime > 0 ? '0' : '3'); const baseUrl = reportUrl.startsWith('http') ? reportUrl : `${protocol}//${host}${reportUrl}`; const reportsUrl = `${baseUrl}/${dtoken}?clazzId=${classId}&playingTime=${currentTime}&duration=${duration}&clipTime=0_${duration}&objectId=${objectId}&otherInfo=${otherInfo}&jobid=${jobId}&userid=${uid}&isdrag=${currentIsdrag}&view=pc&enc=${enc}&rt=0.9&dtype=${mediaType === 'video' ? 'Video' : 'Audio'}&_t=${Date.now()}`; _GM_xmlhttpRequest({ method: "get", url: reportsUrl, headers: { 'Host': host, 'Referer': iframeSrc, 'Sec-Fetch-Site': 'same-origin', 'Content-Type': 'application/json' }, onload: function(res) { try { const result = JSON.parse(res.responseText); if (result.isPassed) { logStore.addLog(`视频任务点已完成`, "success"); resolveReport(true); } else { resolveReport(false); } } catch (e) { logStore.addLog(`上报响应解析失败(status=${res.status}): ${res.responseText.substring(0, 200)}`, "danger"); resolveReport(false); } }, onerror: function(err) { logStore.addLog(`上报请求失败`, "danger"); resolveReport(false); } }); }); }; if (!reportUrl) { logStore.addLog(`⚠️ reportUrl为空,将使用默认路径/multimedia/v2`, "warning"); reportUrl = '/multimedia/v2'; } if (directComplete) { logStore.addLog(`⚠️ 直接上报中...`, "warning"); progressStore.update({ taskName: `[${mediaType === 'video' ? '视频' : '音频'}] ${videoName}`, percent: 100, currentTime: duration, totalTime: duration, type: mediaType === 'video' ? '视频' : '音频', detail: '直接上报', isPlaying: true, speedDisabled: speedDisabled }); const isComplete = await reportProgress(duration, true); if (isComplete) { completedSimulatedIds.add(objectId); logStore.addLog(`🎬 ${mediaType}直接上报`, "success"); progressStore.update({ percent: 100, currentTime: duration, detail: '已完成', isPlaying: false }); return safeResolve(); } else { logStore.addLog(`直接上报失败,回退到模拟播放`, "danger"); } } if (window._currentMediaInterval) { clearInterval(window._currentMediaInterval); window._currentMediaInterval = null; } progressStore.update({ taskName: `[${mediaType === 'video' ? '视频' : '音频'}] ${videoName}`, percent: 0, currentTime: 0, totalTime: duration, type: mediaType === 'video' ? '视频' : '音频', detail: `${formatDuration(0)}/${formatDuration(duration)}`, isPlaying: true, speedDisabled: speedDisabled }); let playTime = 0; let playsTime = 0; let isFirst = true; let nextReportTime = 0; let isdrag = '3'; let completeRetryCount = 0; const maxCompleteRetries = 10; const reportInterval = 50; let isReporting = false; const afkEnabled = configStore.platformParams.cx.parts[4]?.params[0]?.value || false; const simulateLoopId = 'simulate_' + Date.now(); let loopInterval = null; const loopCallback = async () => { if (isReporting) return; if (!window._simulateActive) { logStore.addLog('模拟播放已被停止,结束处理', 'warning'); if (afkEnabled) { BackgroundWorker.stop(simulateLoopId); } else { clearInterval(loopInterval); } window._currentMediaInterval = null; progressStore.update({ isPlaying: false }); releaseLock(); safeResolve(); return; } const hasMedia = hasVideoOrAudio(); if (!hasMedia) { window._simulateActive = false; logStore.addLog('检测到视频/音频已消失,停止模拟播放(未完成)', 'warning'); if (afkEnabled) { BackgroundWorker.stop(simulateLoopId); } else { clearInterval(loopInterval); } window._currentMediaInterval = null; progressStore.update({ isPlaying: false }); clearOverlayAndBanner(); releaseLock(); safeResolve(); return; } const autoMaxRateNow = configStore.platformParams.cx.parts[0].params[5].value || false; let playbackRateNow = autoMaxRateNow ? maxRate : Math.min(playbackRateParam.value || playbackRate, 3); let effectiveRate = playbackRateNow; if (evasionEnabled) { effectiveRate = getEvasionRate(effectiveRate); effectiveRate = getSmartSpeed(effectiveRate); simulateUserBehavior(); } playsTime += effectiveRate; playTime = Math.ceil(playsTime); playTime = compensateDuration(playTime, duration, playbackRateNow); if (playTime > duration) { playTime = duration; } const progress = Math.floor((playTime / duration) * 100); progressStore.update({ percent: progress, currentTime: playTime, totalTime: duration, detail: `${formatDuration(playTime)}/${formatDuration(duration)}`, isPlaying: true, speedDisabled: speedDisabled }); let shouldReport = false; if (playTime >= duration) { shouldReport = true; } else if (isFirst) { shouldReport = true; } else if (nextReportTime > 0 && playTime >= nextReportTime) { shouldReport = true; } if (shouldReport) { isReporting = true; if (isFirst) { playTime = 0; isFirst = false; } if (playTime >= duration) { playTime = duration; isdrag = '4'; } else if (playTime > 0) { isdrag = '0'; } nextReportTime = Math.min(playTime + reportInterval, duration); logStore.addLog(`📤 上报进度: ${Math.round(playTime / duration * 100)}%(${Math.round(playTime)}/${Math.round(duration)}s)`); const isComplete = await reportProgress(playTime, isdrag === '4'); isReporting = false; if (isComplete) { if (afkEnabled) { BackgroundWorker.stop(simulateLoopId); } else { clearInterval(loopInterval); } window._simulateActive = false; completedSimulatedIds.add(objectId); logStore.addLog(`🎬 ${mediaType}模拟播放完成`, "success"); progressStore.update({ percent: 100, currentTime: duration, detail: '播放完成', isPlaying: false }); window._currentMediaInterval = null; safeResolve(); } else if (isdrag === '4') { completeRetryCount++; if (completeRetryCount >= maxCompleteRetries) { if (afkEnabled) { BackgroundWorker.stop(simulateLoopId); } else { clearInterval(loopInterval); } window._simulateActive = false; logStore.addLog(`完成上报重试${maxCompleteRetries}次仍未通过,请检查视频是否需要其他操作`, "danger"); progressStore.update({ percent: 100, currentTime: duration, detail: '上报未通过', isPlaying: false }); window._currentMediaInterval = null; safeResolve(); } else { logStore.addLog(`完成上报未通过,重试中(${completeRetryCount}/${maxCompleteRetries})...`, "warning"); } } } }; if (afkEnabled) { window._simulateLoopId = simulateLoopId; window._simulateActive = true; showOverlayAndBanner(); BackgroundWorker.start(simulateLoopId, loopCallback, 1000); logStore.addLog(`🖥️ 挂机模式已启用,使用后台Worker计时`, "success"); } else { loopInterval = setInterval(loopCallback, 1000); window._currentMediaInterval = loopInterval; window._simulateActive = true; showOverlayAndBanner(); } } catch (e) { window._currentMediaInterval = null; window._simulateActive = false; logStore.addLog(`模拟播放出错: ${e.message}`, "danger"); logStore.addLog(`回退到普通播放模式`, "warning"); safeResolve(await playMediaDirectly(mediaType, iframeDocument)); } }); }; const playMediaDirectly = async (mediaType, iframeDocument) => { return new Promise((resolve) => { logStore.addLog(`正在尝试播放${mediaType},请稍等5s`, "primary"); const autoMaxRate = configStore.platformParams.cx.parts[0].params[5].value || false; const playbackRateParam = configStore.platformParams.cx.parts[0].params[6]; let playbackRate = playbackRateParam.value || 1; const maxRate = getMaxPlaybackRate(iframeDocument); const videoQuizEnabled = configStore.platformParams.cx.parts[0].params[4]?.value || false; if (videoQuizEnabled) { const loop = async () => { try { const submitBtn = iframeDocument?.querySelector("#videoquiz-submit"); if (submitBtn) { const list = Array.from(iframeDocument.querySelectorAll(".ans-videoquiz-opt label")); if (list.length > 0) { const answer = list[Math.floor(Math.random() * list.length)]; if (answer) safeClick(answer); if (submitBtn) safeClick(submitBtn); await quickRandomDelay(); const container = iframeDocument.querySelector("#video .ans-videoquiz"); if (container) { container.remove(); } const components = Array.from(iframeDocument.querySelectorAll(".x-component-default")); if (components.length) { for (const com of components) { com.style.display = "none"; } } logStore.addLog("已处理视频内题目", "success"); } } } catch (e) { console.log("处理视频内题目失败:", e); } await quickRandomDelay(); loop(); }; loop(); } const simulatePlayEnabled = configStore.platformParams.cx.parts[0].params[0].value || false; if (!simulatePlayEnabled) { playbackRateParam.max = maxRate; if (playbackRate > maxRate) { playbackRateParam.value = maxRate; playbackRate = maxRate; } } if (autoMaxRate) { playbackRate = maxRate; logStore.addLog(`自动倍速: ${playbackRate}x`, "success"); } else { if (playbackRate > maxRate) { playbackRate = maxRate; logStore.addLog(`倍速已调整为播放器最大值: ${playbackRate}x`, "warning"); } } logStore.addLog(`播放倍速: ${playbackRate}x`, "primary"); let isExecuted = false; logStore.addLog("播放成功", "success"); const intervalId = setInterval(async () => { const mediaElement = iframeDocument.documentElement.querySelector(mediaType); if (mediaElement && !isExecuted) { await mediaElement.pause(); mediaElement.muted = true; await mediaElement.play(); mediaElement.playbackRate = playbackRate; const listener = async () => { await delay(3); await mediaElement.play(); mediaElement.playbackRate = playbackRate; }; mediaElement.addEventListener("pause", listener); mediaElement.addEventListener("ended", () => { logStore.addLog(`${mediaType}已播放完成`, "success"); mediaElement.removeEventListener("pause", listener); resolve(); }); isExecuted = true; clearInterval(intervalId); } }, 2500); }); }; const getMaxPlaybackRate = (iframeDocument, showLog = true) => { try { const menuItems = iframeDocument.querySelectorAll('.vjs-playback-rate .vjs-menu-content .vjs-menu-item'); if (menuItems.length === 0) { return 1; } let maxRate = 1; menuItems.forEach(item => { const text = item.textContent.trim(); const rate = parseFloat(text.replace('x', '')); if (!isNaN(rate) && rate > maxRate) { maxRate = rate; } }); if (showLog) { logStore.addLog(`播放器最大倍速: ${maxRate}x`, "info"); } return maxRate; } catch (e) { logStore.addLog(`获取最大倍速失败,使用默认值1x`, "warning"); return 1; } }; const handleMediaContent = async (mediaType, iframeDocument, iframe) => { const useSimulatePlay = configStore.platformParams.cx.parts[0]?.params[0]?.value || false; if (useSimulatePlay) { return simulateVideoPlay(iframe, iframeDocument, mediaType); } else { return playMediaDirectly(mediaType, iframeDocument); } }; const handleAssignment = async (iframe, iframeDocument, iframeWindow) => { logStore.addLog("发现一个作业,正在解析", "warning"); const taskId = _globalTaskId; const startTime = Date.now(); try{ if (!iframeDocument) { logStore.addLog("iframeDocument为空,无法处理", "danger"); return; } const ansJobIcon = iframe.parentElement?.querySelector(".ans-job-icon"); if (ansJobIcon) { const ariaLabel = ansJobIcon.getAttribute("aria-label") || ""; if (ariaLabel.includes("已完成")) { logStore.addLog("任务点已完成,跳过", "success"); return; } } decodeCipherFont(iframeDocument); logStore.addLog("开始解析题目...", "info"); const handler = new CxQuestionHandler("zj", iframe); const correctRate = await Promise.race([ handler.init(), new Promise((_, reject) => setTimeout(() => reject(new Error("解析超时")), 30000)) ]); if (taskId !== _globalTaskId) { logStore.addLog("任务已取消,停止处理", "warning"); return; } const parseTime = ((Date.now() - startTime) / 1000).toFixed(1); logStore.addLog(`题目解析完成,耗时${parseTime}s,共${handler.questions.length}道题`, "success"); iframeWindow.alert = () => { }; const autoSubmitPart = configStore.platformParams?.cx?.parts?.find(p => p.name === "章节/作业/测验设置"); const autoSubmit = autoSubmitPart?.params?.find(p => p.name === "自动提交")?.value || false; // 调试日志:显示配置读取情况 logStore.addLog(`配置调试: parts数量=${configStore.platformParams?.cx?.parts?.length || 0}`, "info"); if (configStore.platformParams?.cx?.parts) { configStore.platformParams.cx.parts.forEach((part, index) => { logStore.addLog(`Part[${index}]: ${part.name}`, "info"); if (part.params) { part.params.forEach(param => { logStore.addLog(` - ${param.name}: ${param.value}`, "info"); }); } }); } logStore.addLog(`自动提交配置: autoSubmitPart=${autoSubmitPart ? '找到' : '未找到'}, autoSubmit=${autoSubmit}`, "info"); // 详细日志:显示答题统计信息 const totalQuestions = handler.questions.length; const answeredQuestions = handler.questions.filter(q => q.answer && q.answer.length > 0).length; const unansweredQuestions = totalQuestions - answeredQuestions; const localModeQuestions = handler.questions.filter(q => q.source && q.source.startsWith("local")).length; const remoteModeQuestions = totalQuestions - localModeQuestions; logStore.addLog(`答题统计: 总计${totalQuestions}道 | 已答${answeredQuestions}道 | 未答${unansweredQuestions}道`, "info"); logStore.addLog(`答题模式: 本地匹配${localModeQuestions}道 | 远程搜索${remoteModeQuestions}道`, "info"); logStore.addLog(`正确率: ${isNaN(correctRate) ? '0' : correctRate.toFixed(1)}%`, "info"); if (autoSubmit) { logStore.addLog("自动提交已开启,尝试提交", "primary"); const answerParamsPart = configStore.platformParams?.cx?.parts?.find(p => p.name === "答题参数"); const correctRateThreshold = answerParamsPart?.params.find(p => p.name === "正确阈值")?.value || 85; const rate = isNaN(correctRate) ? 0 : correctRate; // 检查是否有离线模式的题目 const hasLocalModeQuestions = handler.questions.some(q => q.source && q.source.startsWith("local")); const allAttempted = handler.questions.every(q => q.answer && q.answer.length > 0); const unansweredCount = handler.questions.filter(q => !q.answer || q.answer.length === 0).length; logStore.addLog(`提交条件: 本地模式=${hasLocalModeQuestions}, 全部已答=${allAttempted}, 未答数=${unansweredCount}`, "info"); logStore.addLog(`正确率: ${rate.toFixed(1)}% >= 阈值: ${correctRateThreshold}%`, "info"); // 关键修复:必须答题!即使正确率低于阈值也要继续尝试答题 // 只有当所有题目都已作答且正确率达到阈值时才提交 const allQuestionsAnswered = handler.questions.every(q => q.answer && q.answer.length > 0); const meetsThreshold = rate >= Number(correctRateThreshold); // 如果还有未答题,继续答题 if (!allQuestionsAnswered) { logStore.addLog(`不满足提交条件: 还有${unansweredCount}道题目未作答,继续答题`, "warning"); return; // 返回继续答题 } // 如果正确率低于阈值,继续尝试提高正确率(重新答题) if (!meetsThreshold) { logStore.addLog(`正确率${rate.toFixed(1)}%低于阈值${correctRateThreshold}%,继续尝试提高正确率`, "warning"); return; // 返回继续答题 } const shouldSubmit = true; if (shouldSubmit) { logStore.addLog(`满足提交条件,准备提交答案...`, "success"); let submitSuccess = false; let submitAttempts = 0; const maxAttempts = 3; while (!submitSuccess && submitAttempts < maxAttempts) { submitAttempts++; logStore.addLog(`提交尝试 ${submitAttempts}/${maxAttempts}`, "info"); // 策略1:尝试学习通标准提交函数 try { if (typeof iframeWindow.btnBlueSubmit === 'function') { await iframeWindow.btnBlueSubmit(); logStore.addLog("使用 btnBlueSubmit() 提交", "success"); submitSuccess = true; } else if (typeof iframeWindow.submitCheckTimes === 'function') { await iframeWindow.submitCheckTimes(); logStore.addLog("使用 submitCheckTimes() 提交", "success"); submitSuccess = true; } else if (typeof iframeWindow.submitWork === 'function') { await iframeWindow.submitWork(); logStore.addLog("使用 submitWork() 提交", "success"); submitSuccess = true; } else if (typeof iframeWindow.submit === 'function') { await iframeWindow.submit(); logStore.addLog("使用 submit() 提交", "success"); submitSuccess = true; } } catch (e) { logStore.addLog(`函数提交失败: ${e.message}`, "warning"); } // 策略2:查找提交按钮(扩展选择器列表) if (!submitSuccess) { const submitSelectors = [ 'input[type="submit"][value*="提交"]', 'button[type="submit"]', 'input[value="提交答案"]', 'input[value="提交"]', 'input[value="交卷"]', 'button[onclick*="submit"]', 'button[onclick*="btnBlueSubmit"]', 'button[onclick*="submitWork"]', 'button[onclick*="submitCheckTimes"]', '.submit-btn', '.btn-submit', '#submit', '.submit', '.btn-blue', 'input.btn-blue', 'a[onclick*="submit"]', '.btn-blue-submit', 'button.fs14', // 新增:学习通常见提交按钮选择器 'input[value*="提交"]', 'button:contains("提交")', '.btn-submit-work', '.submit-work', 'input.submit-work', 'button.submit-work', '.btn-complete', 'button.complete', 'input.complete', 'button[type="button"][onclick*="submit"]', 'input[type="button"][onclick*="submit"]', 'div[onclick*="submit"]', 'span[onclick*="submit"]', // 新增:底部固定提交按钮 '.fixed-bottom button', '.footer button', '.bottom-bar button' ]; logStore.addLog(`提交按钮选择器: ${submitSelectors.length}个`, "info"); for (const selector of submitSelectors) { try { const submitBtn = iframeWindow.document.querySelector(selector); if (submitBtn && !submitBtn.disabled && !submitBtn.classList.contains('disabled')) { const btnText = submitBtn.textContent?.trim() || submitBtn.value?.trim() || selector; logStore.addLog(`找到提交按钮: "${btnText}" (${selector})`, "info"); try { submitBtn.scrollIntoView({ block: 'center', behavior: 'smooth' }); await delay(0.5); } catch(e) {} const onclick = submitBtn.getAttribute('onclick'); if (onclick) { try { safeClick(submitBtn); logStore.addLog(`通过onclick执行提交: ${selector}`, 'success'); submitSuccess = true; break; } catch (e) { logStore.addLog(`onclick执行失败: ${e.message}`, 'warning'); } } if (!submitSuccess) { safeClick(submitBtn); logStore.addLog(`点击提交按钮: ${selector}`, 'success'); submitSuccess = true; break; } } } catch (e) { // 忽略单个选择器失败 } } if (!submitSuccess) { logStore.addLog("未找到提交按钮", "warning"); } } // 策略3:通过文本匹配查找提交按钮 if (!submitSuccess) { const allButtons = iframeWindow.document.querySelectorAll('button, input[type="button"], a.btn, .btn, [role="button"], a, div[onclick], span[onclick]'); logStore.addLog(`文本匹配扫描 ${allButtons.length} 个元素`, "info"); for (const btn of allButtons) { const text = btn.textContent?.trim() || btn.value?.trim() || btn.getAttribute('aria-label')?.trim() || ''; if (/^(提交|提交答案|完成|确认提交|交卷|完成答题|确认交卷|提交作业|交作业|完成作业)$/.test(text)) { try { const onclick = btn.getAttribute('onclick'); if (onclick) { try { safeClick(btn); logStore.addLog(`通过文本匹配执行提交: "${text}"`, 'success'); submitSuccess = true; break; } catch (e) { logStore.addLog(`文本匹配onclick失败: ${e.message}`, 'warning'); } } safeClick(btn); logStore.addLog(`通过文本匹配点击提交: "${text}"`, 'success'); submitSuccess = true; break; } catch (e) { logStore.addLog(`文本匹配点击失败: ${e.message}`, 'warning'); } } } if (!submitSuccess) { logStore.addLog("文本匹配未找到提交按钮", "warning"); } } // 策略4:通过事件模拟提交(针对现代前端框架) if (!submitSuccess) { try { const form = iframeWindow.document.querySelector('form'); if (form) { const submitEvent = new Event('submit', { bubbles: true, cancelable: true }); form.dispatchEvent(submitEvent); logStore.addLog('通过表单事件提交', 'success'); submitSuccess = true; } } catch (e) { logStore.addLog(`表单事件提交失败: ${e.message}`, 'warning'); } } // 策略5:暴力遍历所有可点击元素 if (!submitSuccess) { try { const allElements = iframeWindow.document.querySelectorAll('*'); logStore.addLog(`暴力扫描 ${allElements.length} 个元素`, "info"); for (const el of allElements) { const text = el.textContent?.trim() || el.value?.trim() || ''; if (text.length > 0 && text.length < 20 && /^(提交|提交答案|完成|确认提交|交卷|完成答题|确认交卷|提交作业|交作业|完成作业)$/.test(text)) { const style = iframeWindow.getComputedStyle(el); const isVisible = el.offsetParent !== null && el.offsetWidth > 0 && el.offsetHeight > 0 && style.visibility !== 'hidden' && style.display !== 'none'; if (isVisible) { logStore.addLog(`暴力扫描找到提交: "${text}" (${el.tagName}.${el.className?.trim()})`, "info"); const onclick = el.getAttribute('onclick'); if (onclick) { try { safeClick(el); logStore.addLog(`暴力扫描onclick执行提交`, 'success'); submitSuccess = true; break; } catch (e) {} } safeClick(el); logStore.addLog(`暴力扫描点击提交`, 'success'); submitSuccess = true; break; } } } } catch (e) { logStore.addLog(`暴力扫描失败: ${e.message}`, 'warning'); } } // 如果本次尝试失败,等待后重试 if (!submitSuccess && submitAttempts < maxAttempts) { logStore.addLog(`第 ${submitAttempts} 次提交失败,等待后重试...`, "warning"); await quickRandomDelay(); } } if (submitSuccess) { logStore.addLog("提交成功", "success"); await quickRandomDelay(); // ========== 新增:学习提交结果 + 循环重新答题直到100%正确 ========== try { // 等待页面加载出结果 await delay(3); logStore.addLog("🔍 解析提交结果并学习正确答案...", "info"); // 解析提交结果,学习正确答案 const learnedCount = WrongAnswerLearner.parseAndLearnFromResult(iframeWindow, handler.questions); if (learnedCount > 0) { logStore.addLog(`✅ 学习了 ${learnedCount} 个正确答案`, "success"); } // 检查是否有错题需要重新答题 let hasWrongAnswers = false; try { // 检查页面是否有错误标记 const wrongIcons = iframeWindow.document.querySelectorAll('.u-icon-error, .wrong-icon, [class*="error"], [class*="wrong"]'); hasWrongAnswers = wrongIcons && wrongIcons.length > 0; } catch (e) {} if (hasWrongAnswers || learnedCount > 0) { logStore.addLog("🔄 发现需要修正的题目,准备重新答题...", "info"); // 等待一下再返回 await delay(1); // 尝试找到返回/重新答题按钮 let backSuccess = false; const backSelectors = [ 'button:contains("返回")', 'button:contains("重新答题")', 'a:contains("返回")', 'a:contains("重新答题")', '[onclick*="back"]', '[onclick*="retry"]', '.btn-back', '.back-btn' ]; for (const selector of backSelectors) { try { const backBtn = iframeWindow.document.querySelector(selector); if (backBtn) { safeClick(backBtn); logStore.addLog(`点击返回/重新答题按钮: ${selector}`, "success"); backSuccess = true; break; } } catch (e) {} } // 如果没有返回按钮,尝试直接刷新或等待 if (!backSuccess) { logStore.addLog("等待页面刷新...", "warning"); await delay(3); } // 重新开始答题流程 logStore.addLog("♻️ 重新开始答题(使用已学习的正确答案)", "primary"); // 重新调用 parseAndAnswer await parseAndAnswer(iframeWindow); return; } } catch (learnErr) { console.warn('[学习结果] 解析失败:', learnErr); } } else { logStore.addLog("所有提交方式均失败,请手动提交", "danger"); } } else { if (hasLocalModeQuestions) { if (!allAttempted) { logStore.addLog(`部分题目未作答(${unansweredCount}道),暂存`, "warning"); } else { logStore.addLog("所有题目已作答,准备提交", "success"); } } else { logStore.addLog(`正确率${rate.toFixed(1)}%小于${correctRateThreshold}%,暂存`, "danger"); } if(typeof iframeWindow.noSubmit === 'function') await iframeWindow.noSubmit(); } } else { logStore.addLog("未开启自动提交,暂存", "primary"); if(typeof iframeWindow.noSubmit === 'function') await iframeWindow.noSubmit(); } logStore.addLog("作业已完成", "success"); }catch(e){ logStore.addLog(`作业处理异常: ${e.message}`, "danger"); } }; const handleSlideshow = async (iframeWindow) => { logStore.addLog("发现一个PPT,正在解析", "warning"); // 优先尝试 finishJob(与old版本一致,直接通知服务端完成) if (typeof iframeWindow.finishJob === "function") { iframeWindow.finishJob(); await quickRandomDelay(); logStore.addLog("PPT阅读完成", "success"); return Promise.resolve(); } // 检测带音频的PPT(Swiper轮播组件) const swiperContainer = iframeWindow.document.querySelector(".swiper-container"); if (swiperContainer) { // 静音所有音频 iframeWindow.document.querySelectorAll("audio").forEach((audio) => { audio.addEventListener("play", () => { audio.muted = true; }); }); const slides = iframeWindow.document.querySelectorAll(".swiper-container .swiper-slide"); const len = slides.length; logStore.addLog(`检测到带音频PPT,共${len}页,正在翻阅`, "primary"); for (let i = 0; i < len; i++) { if (typeof iframeWindow.swiperNext === "function") { iframeWindow.swiperNext(); } await quickRandomDelay(); } await quickRandomDelay(); logStore.addLog("PPT翻阅完成", "success"); return Promise.resolve(); } // fallback: 滚动到底部 const pptWindow = iframeWindow.document.querySelector("#panView")?.contentWindow; if (pptWindow) { await pptWindow.scrollTo({ top: pptWindow.document.body.scrollHeight, behavior: "smooth" }); await quickRandomDelay(); } logStore.addLog("PPT阅读完成", "success"); return Promise.resolve(); }; const handleEbook = async (iframeWindow) => { logStore.addLog("发现一个电子书,正在解析", "warning"); _unsafeWindow.top.onchangepage(iframeWindow.getFrameAttr("end")); logStore.addLog("阅读完成", "success"); return Promise.resolve(); }; const awaitFrameReady = async (iframe) => { return new Promise((resolve) => { const intervalId = setInterval(async () => { var _a; if (iframe.contentDocument && ((_a = iframe.contentDocument) == null ? void 0 : _a.readyState) == "complete") { resolve(); clearInterval(intervalId); } }, 500); }); }; const handleSingleFrame = async (iframe) => { var _a, _b; stopSimulatePlayIfNeeded(); const iframeSrc = iframe.src; const iframeDocument = iframe.contentDocument; const iframeWindow = iframe.contentWindow; if (!iframeDocument || !iframeWindow) { return Promise.resolve(); } if (iframeSrc.includes("javascript:")) { return Promise.resolve(); } const lastTaskId = _processedIframeTasks.get(iframe); const currentTaskId = _globalTaskId; if (lastTaskId === currentTaskId) { return Promise.resolve(); } _processedIframeTasks.set(iframe, currentTaskId); await awaitFrameReady(iframe); const parentClass = ((_a = iframe.parentElement) == null ? void 0 : _a.className) || ""; if (parentClass.includes("ans-job-finished")) { } else { const _src = iframe.getAttribute('_src') || ''; const matchSrc = iframeSrc.includes("api/work"); const matchSrcAttr = _src.includes("api/work"); const normalMode = configStore.platformParams?.cx?.parts?.[2]?.params?.[2]?.value; const onlyVideo = configStore.platformParams?.cx?.parts?.[2]?.params?.[3]?.value; const onlyAnswer = configStore.platformParams?.cx?.parts?.[2]?.params?.[4]?.value; if (matchSrcAttr && !matchSrc) { return Promise.resolve(); } if (matchSrc || matchSrcAttr) { if (onlyVideo) { if (!hasLoggedSkipTip) { logStore.addLog("仅视频模式,跳过答题", "primary"); hasLoggedSkipTip = true; } return Promise.resolve(); } return handleAssignment(iframe, iframeDocument, iframeWindow); } const ansJobIcon = (_b = iframe.parentElement) == null ? void 0 : _b.querySelector(".ans-job-icon"); if (ansJobIcon) { if (onlyAnswer) { if (!hasLoggedSkipTip) { logStore.addLog("仅答题模式,跳过视频等其他内容", "primary"); hasLoggedSkipTip = true; } return Promise.resolve(); } if (iframeSrc.includes("video")) { return handleMediaContent("video", iframeDocument, iframe); } else if (iframeSrc.includes("audio")) { return handleMediaContent("audio", iframeDocument, iframe); } else if (iframeDocument.querySelector("#img.imglook") || iframeDocument.querySelector(".swiper-container")) { return handleSlideshow(iframeWindow); } else if (iframeSrc.includes("modules/innerbook")) { return handleEbook(iframeWindow); } } } return Promise.resolve(); }; init(); monitorIframes(); watchUrlChanges(); }; const clickOption = (element) => { try{ if(!element) return; // 先检查是否已选中 const isChecked = element.getAttribute("aria-checked") === "true" || element.classList.contains("is-checked") || element.classList.contains("selected") || element.classList.contains("answer-selected"); if(isChecked) return; // 已选中,无需重复点击 // 聚焦元素 element.focus(); // 模拟完整的人类点击行为序列 element.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true })); element.dispatchEvent(new MouseEvent('mouseup', { bubbles: true, cancelable: true })); element.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true })); element.click(); // 尝试点击内部input元素(如果有) const input = element.querySelector('input[type="radio"], input[type="checkbox"]'); if(input) { input.focus(); input.click(); } // 尝试点击内部label元素(如果有) const label = element.querySelector('label'); if(label) { label.click(); } }catch(e){} }; const useStuActiveLogic = async () => { try { window.parent.postMessage({ source: 'chaoxing-helper-iframe', action: 'closePanel' }, '*'); } catch (e) {} const logStore = useLogStore(); const questionStore = useQuestionStore(); const progressStore = useProgressStore(); const configStore = useConfigStore(); logStore.addLog(`进入随堂练习答题页面`, "primary"); logStore.addLog(`等待Vue渲染题目...`, "warning"); progressStore.update({ taskName: "AI答题", percent: 0, type: "答题", detail: "正在解析题目...", isPlaying: true }); decodeCipherFont(document); let questionItems = null; for (let i = 0; i < 60; i++) { await delay(0.5); questionItems = document.querySelectorAll(".question-item"); if (questionItems.length > 0) break; } if (!questionItems || questionItems.length === 0) { logStore.addLog("未解析到题目,可能页面尚未加载完成", "danger"); logStore.addLog("请刷新页面重试", "warning"); progressStore.update({ taskName: "暂无任务", percent: 0, type: "-", detail: "解析题目失败", isPlaying: false }); return; } const questionTypeMapping = { "单选题": "0", "多选题": "1", "判断题": "3", "填空题": "2" }; const questions = []; questionItems.forEach((questionItem) => { const questionNameEl = questionItem.querySelector(".question-name"); const questionText = questionNameEl ? questionNameEl.innerText.trim() : ""; let questionTypeText = "单选题"; if (questionItem.classList.contains("multiple-choice")) { questionTypeText = "多选题"; } else if (questionText.includes("判断题")) { questionTypeText = "判断题"; } else if (questionText.includes("填空题")) { questionTypeText = "填空题"; } const cleanTitle = questionText .replace(/^\d+\.\s*/, "") .replace(/\[单选题\]|\[多选题\]|\[判断题\]|\[填空题\]/g, "") .trim(); const optionLis = questionItem.querySelectorAll(".option-list li"); const optionsObject = {}; const optionTexts = []; optionLis.forEach((li) => { const result = li.querySelector(".option-result")?.innerText?.trim() || ""; optionsObject[result] = li; optionTexts.push(result); }); questions.push({ element: questionItem, type: questionTypeMapping[questionTypeText] || "0", title: cleanTitle, optionsText: optionTexts, options: optionsObject, answer: [], workType: "stuActive", refer: window.location.href }); }); logStore.addLog(`成功解析到${questions.length}道题目`, "success"); progressStore.update({ taskName: `AI答题 (共${questions.length}题)`, percent: 0, type: "答题", detail: `0/${questions.length} 已完成`, isPlaying: true }); const answerParamsPart = configStore.platformParams.cx?.parts.find(p => p.name === "答题参数"); const skipAnswered = answerParamsPart?.params.find(p => p.name === "跳过已答")?.value || false; const answerInterval = answerParamsPart?.params.find(p => p.name === "答题间隔")?.value || 1; const useSimilarity = answerParamsPart?.params.find(p => p.name === "相似匹配")?.value || false; const simulateDelay = answerParamsPart?.params.find(p => p.name === "模拟延迟")?.value ?? true; let skippedCount = 0; for (const [index, question] of questions.entries()) { const isAnswered = Array.from(question.element.querySelectorAll(".option-list li")).some(li => li.classList.contains("active")); if (skipAnswered && isAnswered) { logStore.addLog(`第${index + 1}题已作答,跳过`, "warning"); skippedCount += 1; questionStore.addQuestion(question); continue; } logStore.addLog(`正在查找第${index + 1}道题目答案...`, "primary"); const answerData = await queryAnswer(question); if (answerData.code === 499) { break; } if (answerData.code === 200) { question.answer = answerData.data.answer; question.source = answerData.data.source; if (question.type === "0" || question.type === "1" || question.type === "3") { const selectedKeys = new Set(); for (const answer of question.answer) { const cleanAnswer = answer.replace(/<[^>]*>/g, "").trim(); let matched = false; // 策略1:精确匹配选项文本 for (const key in question.options) { if (key === cleanAnswer && !selectedKeys.has(key)) { matched = true; selectedKeys.add(key); clickOption(question.options[key]); await randomDelay(0.1, 0.15); break; } } // 策略2:去除选项前缀后匹配(如 "A. xxx" -> "xxx") if (!matched) { const cleanAnswerNoPrefix = cleanAnswer.replace(/^[A-Z][.、]\s*/, '').trim(); for (const key in question.options) { const cleanKey = key.replace(/^[A-Z][.、]\s*/, '').trim(); if (cleanKey === cleanAnswerNoPrefix && !selectedKeys.has(key)) { matched = true; selectedKeys.add(key); clickOption(question.options[key]); await randomDelay(0.1, 0.15); break; } } } // 策略3:包含匹配(答案包含在选项中,或选项包含在答案中) if (!matched) { for (const key in question.options) { const cleanKey = key.replace(/<[^>]*>/g, "").trim(); if ((cleanKey.includes(cleanAnswer) || cleanAnswer.includes(cleanKey)) && cleanAnswer.length > 2 && !selectedKeys.has(key)) { matched = true; selectedKeys.add(key); clickOption(question.options[key]); await randomDelay(0.1, 0.15); break; } } } // 策略4:相似度匹配兜底(使用标准化匹配) if (!matched) { // 【优化】使用更高的阈值 60% 减少误匹配 const bestMatch = pickBestOption(cleanAnswer, question.options, 60); if (bestMatch && !selectedKeys.has(bestMatch.key)) { matched = true; selectedKeys.add(bestMatch.key); clickOption(question.options[bestMatch.key]); await randomDelay(0.1, 0.15); } } if (!matched) { logStore.addLog(`答案填写失败: "${cleanAnswer.substring(0, 20)}..." 无法匹配选项`, 'warning'); } } } const aiSources = ['hunyuan-standard', 'hunyuan-t1', 'DeepSeek-V3.2-Think', 'DeepSeek-V3.2', 'DeepSeek-R1-0528', 'qwen3.6-plus', 'qwen3.5-plus', 'minimax-m2.5', 'minimax-m2.7', 'gpt-5.4-mini', 'gpt-5.4-nano', 'gemini-3.1-flash-lite-preview', 'Pro/zai-org/GLM-5', 'Pro/zai-org/GLM-5.1', 'Pro/zai-org/GLM-4.7']; const isFromAI = aiSources.includes(answerData.data.source); const sourceHint = isFromAI ? `(${answerData.data.source})` : ""; const msgHint = answerData.msg ? ` - ${answerData.msg}` : ""; logStore.addLog(`第${index + 1}道题搜索成功${sourceHint}${msgHint}`, "success"); // 关键修复:所有答题成功后都需要扣减次数 try { console.log('[学习通助手] 随堂练习答题成功,开始扣减次数, source:', answerData.data.source); const consumed = await ClientLicense.consumeOne(); console.log('[学习通助手] consumeOne 返回:', consumed); if (consumed.ok) { logStore.addLog(`✅ 次数已扣减,剩余: ${consumed.uses_remaining}次`, "success"); // 剩余次数不足时提醒购买 if (typeof consumed.uses_remaining === 'number' && consumed.uses_remaining <= 5 && consumed.uses_remaining > 0) { logStore.addLog(`⚠️ 剩余次数较少(${consumed.uses_remaining}次),建议及时充值`, 'warning'); logStore.addLog('🛒 点击购买卡密,获取更多答题次数', 'warning'); } } else { // 次数耗尽时明确提醒购买 if (consumed.message === 'exhausted') { logStore.addLog('❌ 答题次数已用完,无法继续答题', 'danger'); logStore.addLog('🛒 点击购买卡密,享无限答题', 'warning'); } else if (consumed.message === 'tampered') { logStore.addLog('❌ 授权异常:检测到篡改', 'danger'); } else { logStore.addLog(`⚠️ 次数扣减失败: ${consumed.message}`, "warning"); } } } catch (e) { console.error('[学习通助手] 扣减次数异常:', e); logStore.addLog(`❌ 扣减次数异常: ${e.message}`, "danger"); } } else { if (answerData.code === 403 && answerData.data && answerData.data.limitedMode) { logStore.addLog(`第${index + 1}道题搜索失败: 免费题库无答案`, "danger"); question.answer[0] = answerData.msg; } else if (answerData.code === 429 && answerData.data && answerData.data.limitedMode) { logStore.addLog(`第${index + 1}道题搜索失败: 今日免费查题已达上限`, "danger"); logStore.addLog('💎 点击购买Token,享无限查题', 'warning'); question.answer[0] = answerData.msg; } else { logStore.addLog(`第${index + 1}道题搜索失败: ${answerData.msg}`, "danger"); question.answer[0] = answerData.msg; } } questionStore.addQuestion(question); const completedCount = index + 1 - skippedCount; const progressPercent = Math.round((completedCount / questions.length) * 100); const confidence = answerData.data?.confidence || 0; const qualityIcon = confidence > 70 ? '✅' : confidence > 50 ? '⚠️' : '❓'; progressStore.update({ taskName: `AI答题 (共${questions.length}题)`, percent: progressPercent, type: "答题", detail: `${completedCount}/${questions.length} ${qualityIcon} 已完成`, isPlaying: true }); await (simulateDelay ? randomDelay(Number(answerInterval), 0.5) : delay(Number(answerInterval))); } if (skippedCount > 0) { logStore.addLog(`共跳过${skippedCount}道已答题目`, "primary"); } logStore.addLog("随堂练习答题完成", "success"); progressStore.update({ taskName: "答题完成", percent: 100, type: "答题", detail: "所有题目已处理完毕 ✅", isPlaying: false }); const autoSubmit = configStore.platformParams.cx.parts[2]?.params.find(p => p.name === "自动提交")?.value; if (autoSubmit) { logStore.addLog("自动提交已开启,准备提交...", "warning"); await quickRandomDelay(); let submitSuccess = false; // 策略1:尝试学习通标准提交函数 try { if (typeof window.btnBlueSubmit === 'function') { window.btnBlueSubmit(); logStore.addLog("使用 btnBlueSubmit() 提交", "success"); submitSuccess = true; } else if (typeof window.submitCheckTimes === 'function') { window.submitCheckTimes(); logStore.addLog("使用 submitCheckTimes() 提交", "success"); submitSuccess = true; } else if (typeof window.submitWork === 'function') { window.submitWork(); logStore.addLog("使用 submitWork() 提交", "success"); submitSuccess = true; } } catch (e) { logStore.addLog(`函数提交失败: ${e.message}`, "warning"); } // 策略2:查找提交按钮 if (!submitSuccess) { const submitSelectors = [ 'input[type="submit"][value*="提交"]', 'button[type="submit"]', 'input[value="提交答案"]', 'input[value="提交"]', 'button[onclick*="submit"]', 'button[onclick*="btnBlueSubmit"]', '.submit-btn', '.btn-submit', '#submit', '.submit' ]; for (const selector of submitSelectors) { const submitBtn = document.querySelector(selector); if (submitBtn && !submitBtn.disabled && !submitBtn.classList.contains('disabled')) { try { submitBtn.scrollIntoView({ block: 'center', behavior: 'smooth' }); await delay(0.5); const onclick = submitBtn.getAttribute('onclick'); if (onclick) { try { clickOption(submitBtn); logStore.addLog(`通过onclick执行提交`, 'success'); submitSuccess = true; break; } catch (e) {} } if (!submitSuccess) { clickOption(submitBtn); logStore.addLog(`点击提交按钮: ${selector}`, 'success'); submitSuccess = true; break; } } catch (e) { logStore.addLog(`点击提交按钮失败: ${e.message}`, 'warning'); } } } } // 策略3:通过文本匹配查找提交按钮 if (!submitSuccess) { const allButtons = document.querySelectorAll('button, input[type="button"], a.btn'); for (const btn of allButtons) { const text = btn.textContent?.trim() || ''; if (/^(提交|提交答案|完成|确认提交)$/.test(text)) { try { const onclick = btn.getAttribute('onclick'); if (onclick) { try { clickOption(btn); logStore.addLog(`通过文本匹配执行提交`, 'success'); submitSuccess = true; break; } catch (e) {} } clickOption(btn); logStore.addLog(`通过文本匹配点击提交`, 'success'); submitSuccess = true; break; } catch (e) {} } } } if (submitSuccess) { logStore.addLog("已执行提交,等待确认...", "info"); await quickRandomDelay(); // 处理提交确认弹窗 const confirmDialog = document.querySelector('.layui-layer, .confirm-dialog, .submit-confirm'); if (confirmDialog) { const confirmBtn = confirmDialog.querySelector('button[value*="提交"], .layui-layer-btn0, .confirm-btn, .ok-btn'); if (confirmBtn) { clickOption(confirmBtn); logStore.addLog("已确认提交", "success"); } } logStore.addLog("提交完成", "success"); // 自动跳转下一章节 const autoSwitch = configStore.platformParams?.cx?.parts?.[2]?.params?.[1]?.value; if (autoSwitch) { logStore.addLog("自动切换已开启,准备跳转下一章节", "success"); // 跳转前状态检查 let navigationSuccess = false; let navigationAttempts = 0; const maxNavigationAttempts = 3; while (!navigationSuccess && navigationAttempts < maxNavigationAttempts) { navigationAttempts++; logStore.addLog(`跳转尝试 ${navigationAttempts}/${maxNavigationAttempts}`, "info"); // 使用快速随机延迟替代固定3秒 await quickRandomDelay(); // 多种选择器查找下一章节按钮 const nextSelectors = [ "#prevNextFocusNext", ".jb_btn.jb_btn_92.fr.fs14.nextChapter", "#nextBtn", ".nextChapter", "a[href*='next']", "button[onclick*='next']", ".btn-next", ".next-btn", "[data-action='next']", "[title*='下一']", "[aria-label*='下一']", "a:contains('下一章节')", "button:contains('下一章节')", ".chapter-nav .next", ".nav-next", "#next-chapter" ]; let targetBtn = null; let foundSelector = ''; // 策略1:通过选择器查找 for (const selector of nextSelectors) { try { const btn = document.querySelector(selector); if (btn && btn.offsetParent !== null) { // 检查元素是否可见 const rect = btn.getBoundingClientRect(); if (rect.width > 0 && rect.height > 0) { // 检查尺寸 targetBtn = btn; foundSelector = selector; break; } } } catch (e) { // 忽略选择器错误 } } // 策略2:通过文本匹配查找 if (!targetBtn) { const allButtons = document.querySelectorAll('button, a.btn, .btn, [role="button"], .jb_btn'); for (const btn of allButtons) { const text = btn.textContent?.trim() || btn.value?.trim() || btn.getAttribute('aria-label')?.trim() || ''; if (/^(下一章节|下一题|下一章|继续|下一步|Next|next)$/.test(text)) { targetBtn = btn; foundSelector = `text:"${text}"`; break; } } } // 策略3:查找包含"下一"关键词的元素 if (!targetBtn) { const allLinks = document.querySelectorAll('a, button'); for (const el of allLinks) { const text = el.textContent?.trim() || ''; if (text.includes('下一') && text.includes('章')) { targetBtn = el; foundSelector = `link:"${text}"`; break; } } } if (targetBtn) { logStore.addLog(`找到下一章节按钮 (${foundSelector}),准备点击`, "success"); try { // 滚动到按钮位置 targetBtn.scrollIntoView({ block: 'center', behavior: 'smooth' }); await quickRandomDelay(); // 尝试多种点击方式 const onclick = targetBtn.getAttribute('onclick'); if (onclick) { try { clickOption(targetBtn); logStore.addLog(`通过onclick执行跳转`, 'success'); navigationSuccess = true; break; } catch (e) { logStore.addLog(`onclick执行失败: ${e.message}`, 'warning'); } } if (!navigationSuccess) { clickOption(targetBtn); logStore.addLog(`已点击下一章节按钮`, "success"); navigationSuccess = true; } } catch (e) { logStore.addLog(`点击下一章节按钮失败: ${e.message}`, "warning"); } } else { logStore.addLog(`未找到下一章节按钮 (尝试 ${navigationAttempts}/${maxNavigationAttempts})`, "warning"); } // 如果本次尝试失败,等待后重试 if (!navigationSuccess && navigationAttempts < maxNavigationAttempts) { logStore.addLog(`第 ${navigationAttempts} 次跳转失败,等待后重试...`, "warning"); await quickRandomDelay(); } } if (navigationSuccess) { logStore.addLog("已成功跳转至下一章节", "success"); } else { logStore.addLog("所有跳转尝试均失败,请手动跳转", "danger"); } } } else { logStore.addLog("未找到提交按钮,请手动提交", "danger"); } } else { logStore.addLog("未开启自动提交,请手动提交", "info"); } }; const useCxWorkLogic = async () => { const logStore = useLogStore(); useConfigStore(); logStore.addLog(`进入新版作业页面,开始准备答题`, "primary"); logStore.addLog(`正在解析题目, 请等待5s`, "warning"); try{ await Promise.race([ new CxQuestionHandler("zy").init(), new Promise((_, reject) => setTimeout(() => reject(new Error("解析超时")), 30000)) ]); }catch(e){ logStore.addLog(`解析题目异常: ${e.message}`, "danger"); } }; const useCxExamLogic = async () => { var _a; const logStore = useLogStore(); const configStore = useConfigStore(); logStore.addLog(`进入新版考试页面,开始准备答题`, "primary"); logStore.addLog(`正在解析题目, 请等待5s`, "warning"); try{ await Promise.race([ new CxQuestionHandler("ks").init(), new Promise((_, reject) => setTimeout(() => reject(new Error("解析超时")), 30000)) ]); if (configStore.platformParams.cx.parts[3].params[0].value) { const currentQuestionNum = parseInt(((_a = _unsafeWindow.document.querySelector(".topicNumber_list .current")) == null ? void 0 : _a.innerText) || "0"); const totalQuestions = _unsafeWindow.document.querySelectorAll(".topicNumber_list li").length; if (currentQuestionNum >= totalQuestions) { logStore.addLog("当前已是最后一题,不再自动切换", "warning"); logStore.addLog("请手动检查答案后提交试卷", "primary"); } else { logStore.addLog("自动切换已开启,正在前往下一题", "success"); await quickRandomDelay(); if(typeof _unsafeWindow.getTheNextQuestion === 'function') _unsafeWindow.getTheNextQuestion(1); } } else { logStore.addLog("已经关闭自动切换,在设置里可更改", "danger"); } }catch(e){ logStore.addLog(`解析题目异常: ${e.message}`, "danger"); } }; class ZhsQuestionHandler extends QuestionProcessor { constructor() { super(); __publicField(this, "isZhsQuestionAnswered", (question) => { const reverseTypeMapping = { "0": "单选题", "1": "多选题", "2": "填空题", "3": "判断题", "4": "简答题", "5": "名词解释", "6": "论述题", "7": "计算题" }; const questionTypeText = reverseTypeMapping[question.type] || question.type; if (questionTypeText === "单选题" || questionTypeText === "多选题") { for (const key in question.options) { const optionElement = question.options[key]; if (optionElement.classList.contains("cur") || optionElement.classList.contains("selected") || optionElement.querySelector(".onChecked") || optionElement.querySelector(".cur") || optionElement.querySelector(".selected")) { return true; } } return false; } if (questionTypeText === "判断题") { for (const key in question.options) { const optionElement = question.options[key]; if (optionElement.classList.contains("cur") || optionElement.classList.contains("selected") || optionElement.querySelector(".onChecked")) { return true; } } return false; } return false; }); __publicField(this, "init", async () => { var _a; this.questions = []; this.parseHtml(); if (this.questions.length) { this.addLog(`成功解析到${this.questions.length}个题目`, "primary"); const configStore = useConfigStore(); const _answerParamsPart2 = configStore.platformParams[configStore.platformName]?.parts.find(p => p.name === "答题参数"); const skipAnswered = _answerParamsPart2?.params.find(p => p.name === "跳过已答")?.value || false; let skippedCount = 0; for (const [index, question] of this.questions.entries()) { if (skipAnswered && this.isZhsQuestionAnswered(question)) { this.addLog(`第${index + 1}道题已作答,跳过`, "warning"); skippedCount += 1; this.addQuestion(question); const switchBtns = (_a = this._document) == null ? void 0 : _a.querySelectorAll(".switch-btn-box > button"); if (switchBtns && switchBtns[1]) safeClick(switchBtns[1]); await new Promise(r => setTimeout(r, 1500)); continue; } this.addLog(`正在查找第${index + 1}道题目答案...`, "primary"); const answerData = await queryAnswer(question); if (answerData.code === 200) { question.answer = answerData.data.answer; question.source = answerData.data.source; this.addQuestion(question); await new Promise(r => setTimeout(r, 100)); await this.fillQuestion(question); const aiSources = ['hunyuan-standard', 'hunyuan-t1', 'DeepSeek-V3.2-Think', 'DeepSeek-V3.2', 'DeepSeek-R1-0528', 'qwen3.6-plus', 'qwen3.5-plus', 'minimax-m2.5', 'minimax-m2.7', 'gpt-5.4-mini', 'gpt-5.4-nano', 'gemini-3.1-flash-lite-preview', 'Pro/zai-org/GLM-5', 'Pro/zai-org/GLM-5.1', 'Pro/zai-org/GLM-4.7']; const isFromAI = aiSources.includes(answerData.data.source); const sourceHint = isFromAI ? `(${answerData.data.source})` : ""; const msgHint = answerData.msg ? ` - ${answerData.msg}` : ""; this.addLog(`第${index + 1}道题搜索成功${sourceHint}${msgHint}`, "success"); // 关键修复:所有答题成功后都需要扣减次数 try { console.log('[学习通助手] 智慧树答题成功,开始扣减次数, source:', answerData.data.source); const consumed = await ClientLicense.consumeOne(); console.log('[学习通助手] consumeOne 返回:', consumed); if (consumed.ok) { this.addLog(`✅ 次数已扣减,剩余: ${consumed.uses_remaining}次`, "success"); // 剩余次数不足时提醒购买 if (typeof consumed.uses_remaining === 'number' && consumed.uses_remaining <= 5 && consumed.uses_remaining > 0) { this.addLog(`⚠️ 剩余次数较少(${consumed.uses_remaining}次),建议及时充值`, 'warning'); this.addLog('🛒 点击购买卡密,获取更多答题次数', 'warning'); } } else { // 次数耗尽时明确提醒购买 if (consumed.message === 'exhausted') { this.addLog('❌ 答题次数已用完,无法继续答题', 'danger'); this.addLog('🛒 点击购买卡密,享无限答题', 'warning'); } else if (consumed.message === 'tampered') { this.addLog('❌ 授权异常:检测到篡改', 'danger'); } else { this.addLog(`⚠️ 次数扣减失败: ${consumed.message}`, "warning"); } } } catch (e) { console.error('[学习通助手] 扣减次数异常:', e); this.addLog(`❌ 扣减次数异常: ${e.message}`, "danger"); } } else { this.addLog(`第${index + 1}道题搜索失败:${answerData.msg}`, "danger"); question.answer[0] = answerData.msg; this.addQuestion(question); await new Promise(r => setTimeout(r, 100)); } const switchBtns2 = (_a = this._document) == null ? void 0 : _a.querySelectorAll(".switch-btn-box > button"); if (switchBtns2 && switchBtns2[1]) safeClick(switchBtns2[1]); await new Promise(r => setTimeout(r, 1500)); } if (skippedCount > 0) { this.addLog(`共跳过${skippedCount}道已答题目`, "primary"); } } else this.addLog("未解析到题目,请刷新重试或进入答题页面", "danger"); }); __publicField(this, "parseHtml", () => { if (!this._document) return []; const questionElements = this._document.querySelectorAll(SELECTORS.ZHS_QUESTION); this.addQuestions(questionElements); }); __publicField(this, "fillQuestion", async (question) => { if (!this._window) return; try { const typeNum = question.type; if (typeNum === "0" || typeNum === "1") { const configStore = useConfigStore(); const useSimilarity = configStore.platformParams[configStore.platformName]?.parts.find(p => p.name === "答题参数")?.params.find(p => p.name === "相似匹配")?.value || false; let hasDeselected = false; for (const key in question.options) { const optionElement = question.options[key]; if (optionElement.classList.contains("cur") || optionElement.classList.contains("selected") || optionElement.querySelector(".onChecked") || optionElement.querySelector(".cur") || optionElement.querySelector(".selected")) { hasDeselected = true; safeClick(optionElement); } } if (hasDeselected) { await new Promise(r => setTimeout(r, 500)); } const selectedKeys = new Set(); console.log("🔍 答案格式:", typeof question.answer, Array.isArray(question.answer), question.answer); console.log("🔍 选项keys:", Object.keys(question.options)); let answers = question.answer; if (typeof question.answer === 'string') { if (question.answer.startsWith('[')) { try { answers = JSON.parse(question.answer); } catch (e) { answers = question.answer.split(/[,,]/).map(a => a.trim()).filter(a => a); } } else { answers = question.answer.split(/[,,]/).map(a => a.trim()).filter(a => a); } console.log("🔍 答案已分割:", answers); } answers.forEach((answer) => { const cleanAnswer = this.stripTags(answer).trim(); let matched = false; for (const key in question.options) { const cleanKey = key.trim(); console.log(`🔍 匹配: "${cleanAnswer}" vs "${cleanKey}" = ${cleanKey === cleanAnswer}`); if (cleanKey === cleanAnswer && !selectedKeys.has(cleanKey)) { matched = true; selectedKeys.add(key); const optionElement = question.options[key]; const isAlreadySelected = optionElement.classList.contains("cur") || optionElement.classList.contains("selected") || optionElement.querySelector(".onChecked") || optionElement.querySelector(".cur") || optionElement.querySelector(".selected"); if (!isAlreadySelected) { optionElement.setAttribute("data-filling", "true"); safeClick(optionElement); setTimeout(() => optionElement.removeAttribute("data-filling"), 200); } break; } } // 策略4:相似度匹配兜底(使用标准化匹配,默认阈值 60%) if (!matched && useSimilarity) { const bestMatch = pickBestOption(cleanAnswer, question.options); if (bestMatch && !selectedKeys.has(bestMatch.key)) { selectedKeys.add(bestMatch.key); const optionElement = question.options[bestMatch.key]; const isAlreadySelected = optionElement.classList.contains("cur") || optionElement.classList.contains("selected") || optionElement.querySelector(".onChecked") || optionElement.querySelector(".cur") || optionElement.querySelector(".selected"); if (!isAlreadySelected) { optionElement.setAttribute("data-filling", "true"); safeClick(optionElement); setTimeout(() => optionElement.removeAttribute("data-filling"), 200); } } } }); } else if (typeNum === "3") { let answer = "错"; if (REGEX.JUDGE_FALSE.test(question.answer[0])) { answer = "错"; } else if (REGEX.JUDGE_TRUE.test(question.answer[0])) { answer = "对"; } for (const key in question.options) { const optionElement = question.options[key]; const isTrueOption = REGEX.JUDGE_TRUE.test(key); const isFalseOption = REGEX.JUDGE_FALSE.test(key); if ((isTrueOption && answer === "对") || (isFalseOption && answer === "错")) { optionElement.setAttribute("data-filling", "true"); safeClick(optionElement); setTimeout(() => optionElement.removeAttribute("data-filling"), 200); break; } } } else if (typeNum === "2") { const textareaElements = question.element.querySelectorAll("textarea"); if (textareaElements.length > 0 && question.answer && question.answer.length > 0) { textareaElements.forEach((textarea, index) => { if (index < question.answer.length) { const answerText = this.stripTags(question.answer[index] || question.answer[0]); textarea.value = answerText; textarea.dispatchEvent(new Event("input", { bubbles: true })); textarea.dispatchEvent(new Event("change", { bubbles: true })); textarea.dispatchEvent(new KeyboardEvent("keydown", { bubbles: true })); textarea.dispatchEvent(new KeyboardEvent("keyup", { bubbles: true })); if(textarea.__vue__){ try{ textarea.__vue__.$emit("input", answerText); }catch(e){} } console.log(`🔍 填空题填入: 第${index + 1}空 = "${answerText}"`); } }); } } else if (["4", "5", "6", "7"].includes(typeNum)) { const textareaElement = question.element.querySelector("textarea"); if (textareaElement && question.answer && question.answer.length > 0) { const answerText = question.answer.map(a => this.stripTags(a)).join("\n"); textareaElement.value = answerText; textareaElement.dispatchEvent(new Event("input", { bubbles: true })); textareaElement.dispatchEvent(new Event("change", { bubbles: true })); textareaElement.dispatchEvent(new KeyboardEvent("keydown", { bubbles: true })); textareaElement.dispatchEvent(new KeyboardEvent("keyup", { bubbles: true })); if(textareaElement.__vue__){ try{ textareaElement.__vue__.$emit("input", answerText); }catch(e){} } console.log(`🔍 简答题填入: "${answerText.substring(0, 50)}..."`); } } else { console.log(`⚠️ 未处理的题型: ${typeNum}`); } } catch (error) { this.addLog(`答题过程发生错误:${error.message}`, "danger"); } }); } extractOptions(optionElements, optionSelector) { const optionsObject = {}; const optionTexts = []; optionElements.forEach((optionElement) => { var _a; const optionTextContent = this.stripTags(((_a = optionElement.querySelector(optionSelector)) == null ? void 0 : _a.innerHTML) || ""); optionsObject[optionTextContent] = optionElement; optionTexts.push(optionTextContent); }); return [optionsObject, optionTexts]; } addQuestions(questionElements) { questionElements.forEach((questionElement) => { var _a, _b, _c, _d; let questionTitle = ""; try{ const titleEl = questionElement == null ? void 0 : questionElement.querySelector(".subject_describe div,.smallStem_describe p"); if(titleEl && titleEl.__Ivue__ && titleEl.__Ivue__._data && titleEl.__Ivue__._data.shadowDom){ questionTitle = titleEl.__Ivue__._data.shadowDom.textContent || ""; } }catch(e){ questionTitle = ""; } const questionTypeText = ((_b = (_a = questionElement == null ? void 0 : questionElement.querySelector(".subject_type span")) == null ? void 0 : _a.textContent) == null ? void 0 : _b.slice(1, 4)) || ""; const zhsTypeMapping = { "单选": "单选题", "多选": "多选题", "填空": "填空题", "判断": "判断题", "简答": "简答题", "名词": "名词解释", "论述": "论述题", "计算": "计算题" }; const fullQuestionType = zhsTypeMapping[questionTypeText] || questionTypeText; const numericType = this.typeMap.get(fullQuestionType) || "999"; const [optionsObject, optionTexts] = this.extractOptions(questionElement == null ? void 0 : questionElement.querySelectorAll(SELECTORS.ZHS_OPTION), ".node_detail"); this.questions.push({ element: questionElement, type: numericType, title: questionTitle, optionsText: optionTexts, options: optionsObject, answer: [], workType: "zhs", refer: this._window.location.href }); }); } } const hookError = () => { console.log("hookError"); const oldset = _unsafeWindow.setInterval; const oldout = _unsafeWindow.setTimeout; _unsafeWindow.setInterval = function(...args) { const err = new Error(); if (err.stack && err.stack.indexOf("checkoutNotTrustScript") !== -1) { return -1; } return oldset.call(this, ...args); }; _unsafeWindow.setTimeout = function(...args) { const err = new Error(); if (err.stack && err.stack.indexOf("checkoutNotTrustScript") !== -1) { return -1; } return oldout.call(this, ...args); }; }; class XMLHttpRequestInterceptor { constructor(urlList, callback) { __publicField(this, "xhr"); __publicField(this, "originalOpen"); __publicField(this, "originalSend"); __publicField(this, "callback"); this.xhr = new XMLHttpRequest(); this.originalOpen = this.xhr.open; this.originalSend = this.xhr.send; this.callback = callback; this.intercept(urlList); } intercept(urlList) { const self = this; XMLHttpRequest.prototype.open = function(method, url2) { self.originalOpen.apply(this, [method, url2]); const shouldIntercept = urlList.some((urlItem) => url2.includes(urlItem)); if (shouldIntercept) { self.callback(this.responseText); } }; } } const useZhsAnswerLogic = async () => { hookError(); const logStore = useLogStore(); useConfigStore(); logStore.addLog(`进入答题页面,开始准备答题`, "primary"); logStore.addLog(`正在解析题目, 请等待5s`, "warning"); new XMLHttpRequestInterceptor(["gateway/t/v1/answer/hasAnswer"], async () => { try{ await quickRandomDelay(); _unsafeWindow.document.getSelection = function() { return { removeAllRanges: function() { } }; }; _unsafeWindow.document.onselectstart = true; _unsafeWindow.document.oncontextmenu = true; _unsafeWindow.document.oncut = true; _unsafeWindow.document.oncopy = true; _unsafeWindow.document.onpaste = true; await Promise.race([ new ZhsQuestionHandler().init(), new Promise((_, reject) => setTimeout(() => reject(new Error("解析超时")), 30000)) ]); }catch(e){ logStore.addLog(`解析题目异常: ${e.message}`, "danger"); } return true; }); }; const _sfc_main$3 = vue.defineComponent({ __name: "Index", emits: ["customEvent"], setup(__props, { emit: __emit }) { var _a; const cardWidth = vue.ref("100%"); const isShow = vue.ref(false); (_a = document.querySelector("li>a.experience:not([onclick])")) == null ? void 0 : _a.click(); const configStore = useConfigStore(); const logStore = useLogStore(); const questionStore = useQuestionStore(); const url2 = window.location.href; logStore.addLog("用户悉知:使用脚本即为完全同意用户协议", "success"); logStore.addLog("脚本加载成功,正在解析网页", "primary"); logStore.addLog("请不要多个脚本同时使用,会有脚本冲突问题", "warning"); logStore.addLog("如果脚本出现异常,请用谷歌、火狐等浏览器", "warning"); // === 版本切换逻辑(参考jinmu.js versionRedirect) === // 旧版学习通URL自动切换到新版(mooc2=1) const isOldVersion = url2.includes("mooc2=0") || url2.includes("mooc-ans/"); if (isOldVersion) { const newUrl = new URL(url2); if (url2.includes("mooc-ans/mycourse/studentstudy")) { newUrl.pathname = "/mycourse/studentstudy"; } newUrl.searchParams.set("mooc2", "1"); newUrl.searchParams.set("newMooc", "true"); logStore.addLog("检测到旧版学习通URL,正在切换到新版...", "warning"); window.location.replace(newUrl.toString()); return; } // === 考试重定向逻辑(参考jinmu.js examRedirect) === // 新版考试页面自动跳转到整卷预览 if (url2.includes("exam-ans/exam/test/reVersionTestStartNew") || url2.includes("mooc-ans/exam/test/reVersionTestStartNew")) { logStore.addLog("检测到新版考试页面,等待跳转到整卷预览...", "info"); // 等待页面加载后自动调用topreview setTimeout(() => { if (typeof _unsafeWindow?.topreview === 'function') { _unsafeWindow.topreview(); } }, 3000); } const urlLogicPairs = [ { keyword: "/mycourse/studentstudy", logic: useCxChapterLogic }, { keyword: "/mooc2/work/dowork", logic: useCxWorkLogic }, { keyword: "/mooc2/exam/preview", logic: useCxExamLogic }, { keyword: "/exam-ans/", logic: useCxExamLogic }, { keyword: "exam/test", logic: useCxExamLogic }, { keyword: "/work/index", logic: useCxWorkLogic }, { keyword: "/work/doHomeWorkNew", logic: useCxWorkLogic }, { keyword: "/work/doTest", logic: useCxWorkLogic }, { keyword: "/work/calcAnswer", logic: useCxWorkLogic }, { keyword: "/work/getAllWork", logic: useCxWorkLogic }, { keyword: "/knowledge/start", logic: useCxChapterLogic }, { keyword: "/ananas/modules/video/", logic: useCxChapterLogic }, { keyword: "/ananas/modules/work/", logic: useCxWorkLogic }, { keyword: "/ztnodedetailcontroller/visitnodedetail", logic: useCxChapterLogic }, { keyword: "mycourse/stu?courseid", logic: () => { logStore.addLog("该页面无任务,请进入章节或答题页面使用", "danger"); } }, { keyword: "/stuExamWeb.html", logic: useZhsAnswerLogic }, { keyword: "answerQuestion2", logic: useStuActiveLogic } ]; const executeLogicByUrl = (url22) => { for (const { keyword, logic } of urlLogicPairs) { if (url22.includes(keyword)) { logic(); isShow.value = true; return; } } isShow.value = false; }; executeLogicByUrl(url2); const emit = __emit; emit("customEvent", isShow.value); const tabs = [ { label: "🏡主页信息", id: "main-log", component: ScriptHome, props: { "log-list": logStore.logList, "server-config": CURRENT_SERVER_CONFIG } }, { label: "📝答题记录", id: "question-record", component: QuestionTable, props: { "question-list": questionStore.questionList } }, { label: "⚙️脚本配置", id: "config-panel", component: ScriptSetting, props: { "global-config": configStore } }, { label: "📖教程解说", id: "tutorial-panel", component: TutorialPanel }, { label: "💬作者的话", id: "author-words", component: AuthorWords }, { label: "🎁授权与兑换", id: "referral-panel", component: ReferralPanel } ]; const activeTab = vue.ref("main-log"); const switchTab = (tabId) => { activeTab.value = tabId; }; return (_ctx, _cache) => { return vue.openBlock(), vue.createElementBlock("div", { style: vue.normalizeStyle({ width: cardWidth.value }), class: "card_content" }, [ vue.createElementVNode("div", { class: "config-tabs-container" }, [ (vue.openBlock(), vue.createElementBlock(vue.Fragment, null, vue.renderList(tabs, (tab) => { const isUnverified = tab.id === "config-panel" && !configStore.tokenVerified; const isActive = activeTab.value === tab.id; // 脚本配置和授权与兑换使用醒目渐变色 const isHighlighted = tab.id === "config-panel" || tab.id === "referral-panel"; const isConfigTab = tab.id === "config-panel"; const isReferralTab = tab.id === "referral-panel"; return vue.createElementVNode("button", { key: tab.id, class: vue.normalizeClass(["config-tab", { active: isActive }]), style: isHighlighted && !isActive ? { "background": isConfigTab ? "linear-gradient(135deg, #f97316, #ef4444)" : "linear-gradient(135deg, #f59e0b, #f97316)", "border": "none", "color": "#ffffff", "font-weight": "bold", "box-shadow": "0 2px 8px rgba(249, 115, 22, 0.4)", "text-shadow": "0 1px 2px rgba(0,0,0,0.2)" } : (isUnverified && !isActive ? { "background": "var(--jb-primary-10)", "border-color": "var(--jb-primary)", "color": "var(--jb-primary)" } : {}), onClick: () => switchTab(tab.id) }, vue.toDisplayString(tab.label), 13, ["onClick", "class", "style"]); }), 64)) ]), (vue.openBlock(), vue.createElementBlock(vue.Fragment, null, vue.renderList(tabs, (tab) => { return vue.withDirectives(vue.createElementVNode("div", { key: tab.id, class: "config-panel" }, [ tab.component ? (vue.openBlock(), vue.createBlock(vue.resolveDynamicComponent(tab.component), vue.mergeProps({ key: 0, ref_for: true }, tab.props), null, 16)) : vue.createCommentVNode("", true) ], 512), [ [vue.vShow, activeTab.value === tab.id] ]); }), 128)) ], 4); }; } }); const _sfc_main$2 = vue.defineComponent({ __name: "ZoomButtons", emits: ["toggleZoom", "closePanel"], setup(__props, { emit: __emit }) { const emit = __emit; const configStore = useConfigStore(); const toggleZoom = () => { const newValue = !configStore.isMinus; emit("toggleZoom", newValue); }; const closePanel = () => { emit("closePanel", false); }; return (_ctx, _cache) => { const _component_el_icon = vue.resolveComponent("el-icon"); return vue.openBlock(), vue.createElementBlock("div", { onMousedown: _cache[1] || (_cache[1] = vue.withModifiers(() => { }, ["stop"])) }, [ vue.createVNode(_component_el_icon, { onClick: _cache[0] || (_cache[0] = () => toggleZoom()), size: "small", style: { "cursor": "pointer", "margin-right": "8px" } }, { default: vue.withCtx(() => [ vue.createVNode(vue.unref(configStore.isMinus ? full_screen_default : minus_default)) ]), _: 1 }), vue.createVNode(_component_el_icon, { onClick: _cache[2] || (_cache[2] = () => closePanel()), size: "small", style: { "cursor": "pointer" } }, { default: vue.withCtx(() => [ vue.createVNode(vue.unref(document_remove_default)) ]), _: 1 }) ], 32); }; } }); const _hoisted_1 = { class: "overlay" }; const _hoisted_2 = { class: "title" }; const _hoisted_3 = { class: "minus" }; const _sfc_main$1 = vue.defineComponent({ __name: "layout", setup(__props) { const isShow = vue.ref(false); const configStore = useConfigStore(); vue.watch(configStore, (newVal) => { _GM_setValue("config", JSON.stringify(newVal)); }, { deep: true }); const isDragging = vue.ref(false); const offsetX = vue.ref(0); const offsetY = vue.ref(0); const moveStyle = vue.computed(() => { return { left: configStore.position.x, top: configStore.position.y, height: configStore.isMinus ? "auto" : "560px", maxHeight: configStore.isMinus ? "none" : "560px", width: configStore.isMinus ? "280px" : "720px", maxWidth: configStore.isMinus ? "280px" : "720px" }; }); const startDrag = (event) => { isDragging.value = true; offsetX.value = event.clientX - event.target.getBoundingClientRect().left; offsetY.value = event.clientY - event.target.getBoundingClientRect().top; document.addEventListener("mousemove", drag); document.addEventListener("mouseup", endDrag); }; const drag = (event) => { if (!isDragging.value) return; const x = event.clientX - offsetX.value; const y = event.clientY - offsetY.value; configStore.position.x = `${x - 11}px`; configStore.position.y = `${y - 11}px`; if (x < 0) { configStore.position.x = "0px"; } if (y < 0) { configStore.position.y = "0px"; } if (x > window.innerWidth - 720) { configStore.position.x = `${window.innerWidth - 720}px`; } if (y > window.innerHeight - 560) { configStore.position.y = `${window.innerHeight - 560}px`; } }; const endDrag = () => { isDragging.value = false; document.removeEventListener("mousemove", drag); document.removeEventListener("mouseup", endDrag); }; const startDragTouch = (event) => { if (event.touches.length === 1) { isDragging.value = true; const touch = event.touches[0]; offsetX.value = touch.clientX - event.target.getBoundingClientRect().left; offsetY.value = touch.clientY - event.target.getBoundingClientRect().top; document.addEventListener("touchmove", dragTouch, { passive: false }); document.addEventListener("touchend", endDragTouch); } }; const dragTouch = (event) => { if (!isDragging.value || event.touches.length !== 1) return; event.preventDefault(); const touch = event.touches[0]; const x = touch.clientX - offsetX.value; const y = touch.clientY - offsetY.value; configStore.position.x = `${x - 11}px`; configStore.position.y = `${y - 11}px`; if (x < 0) { configStore.position.x = "0px"; } if (y < 0) { configStore.position.y = "0px"; } if (x > window.innerWidth - 720) { configStore.position.x = `${window.innerWidth - 720}px`; } if (y > window.innerHeight - 560) { configStore.position.y = `${window.innerHeight - 560}px`; } }; const endDragTouch = () => { isDragging.value = false; document.removeEventListener("touchmove", dragTouch); document.removeEventListener("touchend", endDragTouch); }; return (_ctx, _cache) => { const _component_el_icon = vue.resolveComponent("el-icon"); const _component_el_tooltip = vue.resolveComponent("el-tooltip"); const _component_el_tag = vue.resolveComponent("el-tag"); const _component_el_text = vue.resolveComponent("el-text"); const _component_el_divider = vue.resolveComponent("el-divider"); const _component_el_card = vue.resolveComponent("el-card"); return vue.withDirectives((vue.openBlock(), vue.createElementBlock("div", { style: vue.normalizeStyle(moveStyle.value), class: "main-page" }, [ vue.withDirectives(vue.createElementVNode("div", _hoisted_1, null, 512), [ [vue.vShow, isDragging.value] ]), vue.createVNode(_component_el_card, { style: { "border": "0" }, "close-on-click-modal": false, "lock-scroll": false, modal: false, "show-close": false, "modal-class": "modal" }, { header: vue.withCtx(() => [ vue.createElementVNode("div", { class: "card-header", onMousedown: startDrag, onTouchstart: startDragTouch }, [ vue.createElementVNode("div", _hoisted_2, [ vue.createElementVNode("span", null, vue.toDisplayString(vue.unref(configStore).isMinus ? `超星网课助手 v${vue.unref(configStore).version}` : vue.unref(configStore).platformParams?.[vue.unref(configStore).platformName]?.name || "超星网课助手"), 1), vue.createVNode(_component_el_tooltip, { teleported: "", effect: "dark", placement: "top-start", content: "注意事项: