您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
岐黄天使刷课助手的课程导航功能模块,负责课程的识别、切换和状态管理。
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.gf.qytechs.cn/scripts/537075/1597552/courseNavigation.js
// ==UserScript== // @name 岐黄天使刷课助手 - 课程导航模块 // @namespace http://tampermonkey.net/qhtx-modules // @version 1.3.1 // @description 岐黄天使刷课助手的课程导航功能模块,负责课程的识别、切换和状态管理。 // @author AI助手 // ==/UserScript== // 课程导航模块 (function() { 'use strict'; // 收集课程链接,用于上一课/下一课导航 window.collectCourseLinks = function(doc) { try { // 收集所有课程链接(包括已完成和未完成的) const allCourseLinks = doc.querySelectorAll('.append-plugin-tip > a, .content-unstart a, .content-learning a, .content-finished a'); if (allCourseLinks.length > 0) { console.log('找到课程链接数量:', allCourseLinks.length); window.qh.courseList = Array.from(allCourseLinks).map((link, index) => { return { index: index, title: link.textContent.trim() || `课程 ${index + 1}`, element: link, status: link.closest('.content-finished') ? '已完成' : '未完成' }; }); // 找到当前正在播放的课程 const activeLink = doc.querySelector('.active a'); if (activeLink) { const activeIndex = Array.from(allCourseLinks).findIndex(link => link.href === activeLink.href); if (activeIndex !== -1) { window.qh.currentCourseIndex = activeIndex; console.log('当前课程索引:', window.qh.currentCourseIndex, '课程标题:', window.qh.courseList[window.qh.currentCourseIndex].title); } } else { // 如果找不到活动链接,尝试通过其他方式确定当前课程 const activeItem = doc.querySelector('.active') || doc.querySelector('.content-learning'); if (activeItem) { // 尝试找到最接近的课程链接 const nearestLink = activeItem.querySelector('a'); if (nearestLink) { const nearestIndex = Array.from(allCourseLinks).findIndex(link => link.textContent.trim() === nearestLink.textContent.trim()); if (nearestIndex !== -1) { window.qh.currentCourseIndex = nearestIndex; console.log('通过活动项找到当前课程索引:', window.qh.currentCourseIndex); } } } } // 只有在面板已创建的情况下才更新导航按钮 if (window.qh.panelCreated && document.getElementById('qh-assistant-panel')) { updateNavButtons(); } return; } else { console.log('未找到标准课程链接,尝试查找其他课程元素'); // 尝试查找新的课程结构 (.new_bg 元素) // 处理多层嵌套目录结构 const courseSections = doc.querySelectorAll('.muli1, .xulun'); courseSections.forEach(section => { const titles = section.querySelectorAll('.title'); titles.forEach(title => { const chapterId = title.id || ''; const chapterNum = title.querySelector('b')?.textContent.trim() || ''; const chapterName = title.querySelector('span')?.textContent.trim() || ''; // 获取子课程项 const courseItems = title.nextElementSibling?.querySelectorAll('.new_bg') || []; courseItems.forEach(item => { const courseId = item.id; const onclickFn = item.getAttribute('onclick') || ''; const videoIdMatch = onclickFn.match(/getvideoUrl\((\d+),/); const videoId = videoIdMatch ? videoIdMatch[1] : ''; const titleElement = item.querySelector('.new_biaoti'); let courseTitle = titleElement?.textContent.replace(/^视频\s*/, '') || ''; // 添加data属性 item.dataset.courseId = courseId; item.dataset.videoId = videoId; item.dataset.chapterId = chapterId; window.qh.courseList.push({ id: courseId, videoId: videoId, chapterId: chapterId, title: `${chapterNum} ${chapterName} - ${courseTitle}`, element: item, status: item.querySelector('.text-primary') ? '已完成' : '进行中' }); }); }); }); if (window.qh.courseList.length > 0) { console.log('解析到嵌套课程结构数量:', window.qh.courseList.length); // 使用getCourseList函数获取按顺序排列的课程列表 window.qh.courseList = getCourseList(doc); // 增强活动项检测逻辑 const activeItem = doc.querySelector('.new_bg.active, .new_bg[style*="background"], .studystate.text-success'); if (activeItem) { const newBgElements = doc.querySelectorAll('.new_bg'); const activeIndex = Array.from(newBgElements).indexOf(activeItem); if (activeIndex !== -1) { window.qh.currentCourseIndex = activeIndex; console.log('从新课程结构中找到当前课程索引:', window.qh.currentCourseIndex); } } // 只有在面板已创建的情况下才更新导航按钮 if (window.qh.panelCreated && document.getElementById('qh-assistant-panel')) { updateNavButtons(); } return; } // 尝试从其他元素收集课程信息 const courseItems = doc.querySelectorAll('.course-item, .video-item, li[data-id], .kecheng-item'); if (courseItems.length > 0) { console.log('找到备选课程项数量:', courseItems.length); window.qh.courseList = Array.from(courseItems).map((item, index) => { const link = item.querySelector('a') || item; let title = ''; // 尝试从不同元素获取标题 const titleElement = item.querySelector('.title, .name, .course-name, .video-title'); if (titleElement) { title = titleElement.textContent.trim(); } else { title = item.textContent.trim() || `课程 ${index + 1}`; } // 确定状态 const status = item.classList.contains('finished') || item.classList.contains('complete') || item.querySelector('.complete, .finished, .done') ? '已完成' : '未完成'; return { index: index, title: title, element: link, status: status }; }); // 尝试找到当前活动项 const activeItem = doc.querySelector('.active, .current, .playing, .selected'); if (activeItem) { const activeIndex = Array.from(courseItems).indexOf(activeItem); if (activeIndex !== -1) { window.qh.currentCourseIndex = activeIndex; console.log('从备选项中找到当前课程索引:', window.qh.currentCourseIndex); } } // 只有在面板已创建的情况下才更新导航按钮 if (window.qh.panelCreated && document.getElementById('qh-assistant-panel')) { updateNavButtons(); } } } } catch (e) { console.error('收集课程链接时发生错误:', e); } }; // 从课程列表中收集课程 window.collectCoursesFromList = function(doc) { try { const coursesInList = doc.querySelectorAll('.mycourse-row'); if (coursesInList.length > 0) { window.qh.courseList = Array.from(coursesInList).map((row, index) => { const link = row.querySelector('a'); const title = row.innerText.split('\t')[0].split('\n\n')[1]?.replace("\n", "") || `课程 ${index + 1}`; const status = row.innerText.includes('未完成') || row.innerText.includes('未开始') ? '未完成' : '已完成'; return { index: index, title: title, element: link, status: status }; }); // 只有在面板已创建的情况下才更新导航按钮 if (window.qh.panelCreated && document.getElementById('qh-assistant-panel')) { updateNavButtons(); } } } catch (e) { console.error('从课程列表收集课程时发生错误:', e); } }; // 更新导航按钮状态 window.updateNavButtons = function() { // 延迟执行,确保面板已创建 setTimeout(() => { const prevBtn = document.getElementById('qh-prev-btn'); const nextBtn = document.getElementById('qh-next-btn'); if (!prevBtn || !nextBtn) { console.debug('导航按钮元素未找到,可能面板未完全创建'); return; } // 确保课程列表和索引已初始化 if (!window.qh.courseList) { window.qh.courseList = []; } if (typeof window.qh.currentCourseIndex !== 'number') { window.qh.currentCourseIndex = 0; } // 检查课程列表是否有内容 if (window.qh.courseList.length === 0) { prevBtn.disabled = true; nextBtn.disabled = true; prevBtn.textContent = '上一课'; nextBtn.textContent = '下一课'; console.log('课程列表为空,禁用导航按钮'); // 尝试重新收集课程列表 if (typeof collectCourseLinks === 'function') { console.log('尝试重新收集课程列表...'); collectCourseLinks(document); } } // 更新按钮状态 const canGoPrev = window.qh.currentCourseIndex > 0; const canGoNext = window.qh.currentCourseIndex < window.qh.courseList.length - 1; prevBtn.disabled = !canGoPrev; nextBtn.disabled = !canGoNext; console.log('更新导航按钮状态:', '上一课:', canGoPrev ? '启用' : '禁用', '下一课:', canGoNext ? '启用' : '启用', '当前索引:', window.qh.currentCourseIndex, '课程总数:', window.qh.courseList.length); // 更新按钮文本,显示上一课/下一课的标题 if (window.qh.currentCourseIndex > 0 && window.qh.courseList[window.qh.currentCourseIndex - 1]) { const prevTitle = window.qh.courseList[window.qh.currentCourseIndex - 1].title; prevBtn.title = prevTitle; // 如果标题太长,截断显示 prevBtn.textContent = '上一课: ' + (prevTitle.length > 10 ? prevTitle.substring(0, 10) + '...' : prevTitle); } else { prevBtn.textContent = '上一课'; } if (window.qh.currentCourseIndex < window.qh.courseList.length - 1 && window.qh.courseList[window.qh.currentCourseIndex + 1]) { const nextTitle = window.qh.courseList[window.qh.currentCourseIndex + 1].title; nextBtn.title = nextTitle; // 如果标题太长,截断显示 nextBtn.textContent = '下一课: ' + (nextTitle.length > 10 ? nextTitle.substring(0, 10) + '...' : nextTitle); } else { nextBtn.textContent = '下一课'; } // 重新绑定按钮点击事件,确保它们能正常工作 // 先移除旧的事件监听器,避免重复绑定 const oldPrevHandler = prevBtn.qhPrevHandler; const oldNextHandler = nextBtn.qhNextHandler; if (oldPrevHandler) prevBtn.removeEventListener('click', oldPrevHandler); if (oldNextHandler) nextBtn.removeEventListener('click', oldNextHandler); // 添加新的事件监听器 const newPrevHandler = function() { console.log('上一课按钮点击,当前索引:', window.qh.currentCourseIndex); if (window.qh.currentCourseIndex > 0) { window.qh.currentCourseIndex--; console.log('新课程索引:', window.qh.currentCourseIndex, '课程标题:', window.qh.courseList[window.qh.currentCourseIndex]?.title); navigateToCourse(window.qh.currentCourseIndex); updateNavButtons(); } }; const newNextHandler = function() { console.log('下一课按钮点击,当前索引:', window.qh.currentCourseIndex); if (window.qh.currentCourseIndex < window.qh.courseList.length - 1) { window.qh.currentCourseIndex++; console.log('新课程索引:', window.qh.currentCourseIndex, '课程标题:', window.qh.courseList[window.qh.currentCourseIndex]?.title); navigateToCourse(window.qh.currentCourseIndex); updateNavButtons(); } }; prevBtn.addEventListener('click', newPrevHandler); nextBtn.addEventListener('click', newNextHandler); // 保存处理器引用以便后续移除 prevBtn.qhPrevHandler = newPrevHandler; nextBtn.qhNextHandler = newNextHandler; }, 100); // 延迟100ms执行 }; // 获取课程列表,按照DOM顺序排列 function getCourseList(doc) { try { // 尝试查找所有可能的课程元素 const courseElements = doc.querySelectorAll('.new_bg[onclick*="getvideoUrl"], .new_bg[onclick*="setti"], .kecheng-item[onclick], li[onclick*="getvideoUrl"], li[onclick*="setti"]'); if (courseElements.length === 0) { console.log('未找到课程元素,尝试查找其他类型的课程元素'); console.log('当前DOM结构:', doc.documentElement.outerHTML.substring(0, 500) + '...'); console.log('尝试查找的课程元素选择器:', '.new_bg[onclick*="getvideoUrl"], .new_bg[onclick*="setti"], .kecheng-item[onclick], li[onclick*="getvideoUrl"], li[onclick*="setti"]'); return []; } console.log(`找到 ${courseElements.length} 个可能的课程元素`); if (courseElements.length > 0) { console.log('第一个课程元素信息:', { id: courseElements[0].id, onclick: courseElements[0].getAttribute('onclick'), classList: Array.from(courseElements[0].classList), outerHTML: courseElements[0].outerHTML.substring(0, 200) + '...' }); } const courses = []; // 创建一个映射来存储课程ID和它们的DOM元素 const courseMap = new Map(); courseElements.forEach(item => { const id = item.id || ''; const onclickAttr = item.getAttribute('onclick') || ''; // 提取标题 const titleElement = item.querySelector('.new_biaoti, .title, .name, .course-name'); let title = titleElement ? titleElement.textContent.trim() : ''; // 如果没有找到标题元素,尝试从元素本身获取文本 if (!title && item.textContent) { title = item.textContent.trim(); } // 移除"视频"等前缀 title = title.replace(/^(视频|音频|文档)\s*/, ''); // 确定状态 const statusElement = item.querySelector('.studystate, .status, .complete, .finished'); const status = (statusElement && (statusElement.textContent.includes('已学完') || statusElement.textContent.includes('已完成'))) || item.classList.contains('finished') || item.classList.contains('complete') ? '已完成' : '未完成'; // 尝试从onclick属性中提取课程ID和标题 let courseId = id; let matchFound = false; // 尝试匹配getvideoUrl模式 const getvideoUrlMatch = onclickAttr.match(/getvideoUrl\(([^,]+),\s*['"]([^'"]+)['"]\)/); if (getvideoUrlMatch) { courseId = getvideoUrlMatch[1]; const matchTitle = getvideoUrlMatch[2]; if (!title && matchTitle) { title = matchTitle; } matchFound = true; } // 尝试匹配setti模式 const settiMatch = onclickAttr.match(/setti\(([^)]+)\)/); if (!matchFound && settiMatch) { courseId = settiMatch[1]; matchFound = true; } // 如果找到了匹配,或者onclick属性包含关键字 if (matchFound || onclickAttr.includes('getvideoUrl') || onclickAttr.includes('setti')) { courseMap.set(courseId, { id: courseId, title: title || `课程 ${courseId}`, element: item, status: status, onclickFn: onclickAttr }); } }); // 获取所有可能的章节容器 const chapterContainers = doc.querySelectorAll('.muli1, .chapter, .section, .module'); // 如果找到章节容器,按照DOM顺序遍历章节和课程 if (chapterContainers.length > 0) { console.log(`找到 ${chapterContainers.length} 个章节容器`); let index = 0; chapterContainers.forEach(chapter => { // 查找章节内的所有课程元素 const courseElementsInChapter = chapter.querySelectorAll('.new_bg[onclick], .kecheng-item[onclick], li[onclick]'); courseElementsInChapter.forEach(element => { const id = element.id || ''; const onclickAttr = element.getAttribute('onclick') || ''; // 尝试从onclick属性中提取课程ID let courseId = id; // 尝试匹配getvideoUrl模式 const getvideoUrlMatch = onclickAttr.match(/getvideoUrl\(([^,]+),/); if (getvideoUrlMatch) { courseId = getvideoUrlMatch[1]; } // 尝试匹配setti模式 const settiMatch = onclickAttr.match(/setti\(([^)]+)\)/); if (settiMatch) { courseId = settiMatch[1]; } if (courseMap.has(courseId)) { const course = courseMap.get(courseId); course.index = index++; courses.push(course); } else if (courseMap.has(id)) { const course = courseMap.get(id); course.index = index++; courses.push(course); } }); }); } // 如果通过章节容器没有找到课程,或者找到的课程数量少于总课程数量的一半, // 则直接按照DOM顺序添加所有课程 if (courses.length === 0 || courses.length < courseMap.size / 2) { console.log(`通过章节容器只找到 ${courses.length} 个课程,直接按DOM顺序添加所有课程`); courses.length = 0; // 清空数组 let index = 0; courseElements.forEach(element => { const id = element.id || ''; const onclickAttr = element.getAttribute('onclick') || ''; // 尝试从onclick属性中提取课程ID let courseId = id; // 尝试匹配getvideoUrl模式 const getvideoUrlMatch = onclickAttr.match(/getvideoUrl\(([^,]+),/); if (getvideoUrlMatch) { courseId = getvideoUrlMatch[1]; } // 尝试匹配setti模式 const settiMatch = onclickAttr.match(/setti\(([^)]+)\)/); if (settiMatch) { courseId = settiMatch[1]; } if (courseMap.has(courseId)) { const course = courseMap.get(courseId); course.index = index++; courses.push(course); } else if (courseMap.has(id)) { const course = courseMap.get(id); course.index = index++; courses.push(course); } }); } console.log(`最终找到 ${courses.length} 个课程,按顺序排列`); if (courses.length > 0) { console.log('第一个课程详细信息:', { id: courses[0].id, title: courses[0].title, status: courses[0].status, element: courses[0].element ? courses[0].element.outerHTML.substring(0, 200) + '...' : null }); } return courses; } catch (error) { console.error('获取课程列表出错:', error); // 如果出错,回退到简单的映射方法 const courseElements = doc.querySelectorAll('.new_bg[onclick], .kecheng-item[onclick], li[onclick*="getvideoUrl"], li[onclick*="setti"]'); return Array.from(courseElements).map((item, index) => { // 从onclick属性中提取课程ID和标题 const onclickAttr = item.getAttribute('onclick') || ''; const courseId = item.id || ''; // 提取标题 const titleElement = item.querySelector('.new_biaoti, .title, .name, .course-name'); let title = titleElement ? titleElement.textContent.trim() : ''; // 如果没有找到标题元素,尝试从元素本身获取文本 if (!title && item.textContent) { title = item.textContent.trim(); } // 移除"视频"等前缀 title = title.replace(/^(视频|音频|文档)\s*/, ''); // 确定状态 const statusElement = item.querySelector('.studystate, .status, .complete, .finished'); const status = (statusElement && (statusElement.textContent.includes('已学完') || statusElement.textContent.includes('已完成'))) || item.classList.contains('finished') || item.classList.contains('complete') ? '已完成' : '未完成'; return { index: index, id: courseId, title: title || `课程 ${index + 1}`, element: item, status: status, onclickFn: onclickAttr }; }); } } // 导航到指定索引的课程 function navigateToCourse(index) { console.log('正在切换课程,目标索引:', index); try { if (index < 0 || index >= window.qh.courseList.length) { console.error('无效的课程索引:', index); return; } const targetCourse = window.qh.courseList[index]; if (targetCourse?.element) { console.log('正在跳转至课程:', targetCourse.title); targetCourse.element.click(); // 添加防抖处理防止重复点击 setTimeout(() => updateNavButtons(), 500); } } catch (e) { console.error('课程切换失败:', e); } } // 导航到上一课 window.navigateToPrevCourse = function() { // 动态更新课程列表 const mainFrame = document.getElementById('taocan_main_frame'); const targetDoc = mainFrame ? mainFrame.contentDocument : document; collectCourseLinks(targetDoc); if (window.qh.currentCourseIndex > 0) { console.log('导航到上一课'); const newIndex = window.qh.currentCourseIndex - 1; // 处理iframe嵌套结构 const targetElement = window.qh.courseList[newIndex].element; if (targetElement.closest('iframe')) { const iframe = targetElement.closest('iframe'); iframe.contentWindow.postMessage({ type: 'qh-assistant-nav', index: newIndex, courseList: window.qh.courseList }, '*'); } // 添加防抖处理 clearTimeout(window.qh.navigateTimeout); window.qh.navigateTimeout = setTimeout(() => { window.qh.currentCourseIndex = newIndex; navigateToCourse(newIndex); setTimeout(() => { updateNavButtons(); autoPlayVideo(); }, 1500); }, 500); } else { console.log('已经是第一课,无法导航到上一课'); } }; // 导航到下一课 window.navigateToNextCourse = function() { // 防止重复触发 if (window.qh.isNavigating && window.qh.lastNavigateTime && (Date.now() - window.qh.lastNavigateTime < 3000)) { console.log('导航操作过于频繁,忽略本次请求'); return; } // 记录导航时间 window.qh.lastNavigateTime = Date.now(); window.qh.isNavigating = true; console.log('开始下一课导航流程', { currentIndex: window.qh.currentCourseIndex, courseCount: window.qh.courseList?.length }); // 动态更新课程列表 const mainFrame = document.getElementById('taocan_main_frame'); const targetDoc = mainFrame ? mainFrame.contentDocument : document; collectCourseLinks(targetDoc); if (!window.qh.courseList || window.qh.courseList.length === 0) { console.error('课程列表未初始化'); updateStatus('课程加载中,请稍候...'); collectCourseLinks(document); setTimeout(() => navigateToNextCourse(), 2000); return; } if (window.qh.currentCourseIndex >= window.qh.courseList.length - 1) { console.log('已经是最后一课,无法导航到下一课'); updateStatus('已经是最后一课'); return; } const newIndex = window.qh.currentCourseIndex + 1; console.log('尝试切换到课程索引:', newIndex, '标题:', window.qh.courseList[newIndex]?.title); // 添加iframe支持 const targetElement = window.qh.courseList[newIndex].element; if (targetElement.closest('iframe')) { const iframe = targetElement.closest('iframe'); iframe.contentWindow.postMessage({ type: 'qh-assistant-nav', index: newIndex, courseList: window.qh.courseList }, '*'); } // 添加防抖处理和状态更新 clearTimeout(window.qh.navigateTimeout); window.qh.navigateTimeout = setTimeout(() => { window.qh.currentCourseIndex = newIndex; navigateToCourse(newIndex); setTimeout(() => { if (window.qh.courseList[newIndex]?.element?.offsetParent === null) { console.warn('课程元素不可见,重新收集课程列表'); collectCourseLinks(document); } updateNavButtons(); autoPlayVideo(); // 重置导航状态 setTimeout(() => { window.qh.isNavigating = false; console.log('导航状态已重置'); }, 3000); }, 1500); }, 500); }; })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址