courseNavigation

岐黄天使刷课助手的课程导航功能模块,负责课程的识别、切换和状态管理。

Dette scriptet burde ikke installeres direkte. Det er et bibliotek for andre script å inkludere med det nye metadirektivet // @require https://updategreasyfork.deno.dev/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);
    };
})();