// ==UserScript== // @name 中国保密在线网-自动学习工具 // @namespace http://tampermonkey.net/ // @version 2.9 // @description 自动完成中国保密在线网的视频学习,智能处理不同时长视频,支持跳过已学完课程,支持暂停/继续,优化UI响应速度 // @author 卖炸弹的小女孩 // @match https://www.baomi.org.cn/* // @icon https://file.baomi.org.cn/spc/upload/pagemanagement/2021-10-29/106KXG4GFKMA83255419.png // @grant none // @license MIT // ==/UserScript== (function () { 'use strict'; // 从当前URL获取所有参数 function getAllUrlParams() { const urlParams = new URLSearchParams(window.location.search); const params = {}; for (let [key, value] of urlParams) { params[key] = value; } return params; } // 配置信息 let CONFIG = { coursePacketId: null, delayBetweenRequests: 1500, debugMode: true, maxRetries: 3, urlParams: {}, longVideoThreshold: 600, segmentSize: 300, minSegmentSize: 60 }; // 全局变量 let allCourses = []; let filteredCourses = []; let isRunning = false; let isPaused = false; let currentIndex = 0; let currentProcess = null; let courseDetail = null; let sessionStats = { success: 0, failed: 0, skipped: 0 }; // SVG图标 const SVG_ICONS = { play: '', pause: '', stop: '', check: '', error: '', folder: '', clock: '', settings: '', book: '', time: '', filter: '', skip: '', loading: '' }; // 工具函数:将时间字符串转换为秒数 function timeToSeconds(timeStr) { try { const parts = timeStr.split(':'); if (parts.length === 3) { return parseInt(parts[0]) * 3600 + parseInt(parts[1]) * 60 + parseInt(parts[2]); } return 300; } catch (e) { return 300; } } // 工具函数:获取token function getToken() { const cookieMatch = document.cookie.match(/token=([^;]+)/); if (cookieMatch) return cookieMatch[1]; if (localStorage.getItem('token')) return localStorage.getItem('token'); if (window.authToken) return window.authToken; return null; } // 获取headers function getHeaders() { return { 'Accept': 'application/json, text/plain, */*', 'Content-Type': 'application/json', 'token': getToken(), 'siteId': '95', 'authToken': '3613146b9c1a486b8666e86a78298a1a' }; } async function interruptibleWait(ms) { const checkInterval = 100; let elapsed = 0; while (elapsed < ms) { if (!isRunning) return; await new Promise(resolve => setTimeout(resolve, checkInterval)); elapsed += checkInterval; } } async function checkCourseStatus(course) { const token = getToken(); const resourceDirectoryId = course.SYS_UUID; const url = `https://www.baomi.org.cn/portal/main-api/v2/coursePacket/getResourceUserStatistic?coursePacketId=${CONFIG.coursePacketId}&token=${token}&resourceDirectoryId=${resourceDirectoryId}`; if (CONFIG.debugMode) { console.log(`正在检测课程状态: ${course.name}`, url); } try { const response = await fetch(url, { method: 'GET', headers: getHeaders() }); if (!response.ok) return false; const result = await response.json(); if (result.status === 0 && result.data) { const data = result.data; const isFinished = data.progressRate >= 1.0 || (data.studyLength > 0 && data.studyLength >= data.resourceLength); if (CONFIG.debugMode) { console.log(`状态检测结果 [${course.name}]: 进度 ${(data.progressRate * 100).toFixed(0)}%, 已学 ${data.studyLength}s`); } return isFinished; } return false; } catch (error) { console.warn(`检测课程状态失败 [${course.name}],将尝试默认提交逻辑:`, error); return false; } } // 获取课程详情 async function getCourseDetail() { const { pubId, siteId } = CONFIG.urlParams; if (!pubId || !siteId) return null; const url = `https://www.baomi.org.cn/portal/main-api/resource/simpleDetail.do?pubId=${pubId}&siteId=${siteId}`; try { const response = await fetch(url, { headers: getHeaders() }); const data = await response.json(); return (data.successResult && data.data) ? data.data : null; } catch (error) { return null; } } // 获取目录列表 async function getDirectoryList() { const url = `https://www.baomi.org.cn/portal/main-api/v2/coursePacket/getCourseDirectoryList?scale=1&coursePacketId=${CONFIG.coursePacketId}`; try { const response = await fetch(url, { headers: getHeaders() }); const data = await response.json(); return (data.status === 0 && data.data) ? data.data : []; } catch (error) { return []; } } // 递归获取所有目录 function getAllDirectories(directoryList) { const directories = []; function traverse(dirs) { if (!dirs || !Array.isArray(dirs)) return; dirs.forEach(dir => { directories.push({ SYS_UUID: dir.SYS_UUID, name: dir.name, directoryCascadeID: dir.directoryCascadeID }); if (dir.subDirectory && dir.subDirectory.length > 0) traverse(dir.subDirectory); }); } traverse(directoryList); return directories; } // 获取单个目录下的课程列表 async function getCoursesByDirectory(directoryId, directoryName, retryCount = 0) { const url = `https://www.baomi.org.cn/portal/main-api/v2/coursePacket/getCourseResourceList?coursePacketId=${CONFIG.coursePacketId}&directoryId=${directoryId}&token=${getToken()}`; try { const response = await fetch(url, { headers: getHeaders() }); const data = await response.json(); if (data.status === 0 && data.data && data.data.listdata) { return data.data.listdata.map(course => ({ ...course, directoryName: directoryName, directoryId: directoryId, totalSeconds: timeToSeconds(course.timeLength) })); } return []; } catch (error) { if (retryCount < CONFIG.maxRetries) { await new Promise(resolve => setTimeout(resolve, 1000)); return getCoursesByDirectory(directoryId, directoryName, retryCount + 1); } return []; } } // 获取所有课程 async function getAllCourses() { const directoryList = await getDirectoryList(); if (directoryList.length === 0) return []; const allDirectories = getAllDirectories(directoryList); let allCourses = []; for (let i = 0; i < allDirectories.length; i++) { const directory = allDirectories[i]; const courses = await getCoursesByDirectory(directory.SYS_UUID, directory.name); allCourses = allCourses.concat(courses); if (i < allDirectories.length - 1) await new Promise(resolve => setTimeout(resolve, 300)); } return allCourses; } // 提交单个课程的学习进度 - 智能分段提交 async function submitCourseProgress(course, index, total) { const totalSeconds = course.totalSeconds || timeToSeconds(course.timeLength); const resourceDirectoryId = course.SYS_UUID; const encodedResourceName = encodeURIComponent(course.name); const isLongVideo = totalSeconds > CONFIG.longVideoThreshold; if (isLongVideo) { return await submitLongVideoProgress(course, totalSeconds, resourceDirectoryId, encodedResourceName); } else { return await submitShortVideoProgress(course, totalSeconds, resourceDirectoryId, encodedResourceName); } } // 提交短视频学习进度 async function submitShortVideoProgress(course, totalSeconds, resourceDirectoryId, encodedResourceName) { const params = new URLSearchParams({ courseId: CONFIG.coursePacketId, resourceId: course.resourceID, resourceDirectoryId: resourceDirectoryId, resourceLength: totalSeconds, studyLength: totalSeconds, studyTime: totalSeconds, startTime: Date.now() - (totalSeconds * 1000), resourceName: encodedResourceName, resourceType: '1', resourceLibId: '3', studyResourceId: course.SYS_DOCUMENTID, token: getToken() }); const url = `https://www.baomi.org.cn/portal/main-api/v2/studyTime/saveCoursePackage.do?${params}`; try { const response = await fetch(url, { headers: getHeaders() }); const result = await response.json(); if (result.status === 0) return { success: true, course: course.name, directory: course.directoryName }; return { success: false, course: course.name, error: result.message, directory: course.directoryName }; } catch (error) { return { success: false, course: course.name, error: error.message, directory: course.directoryName }; } } // 提交长视频学习进度 async function submitLongVideoProgress(course, totalSeconds, resourceDirectoryId, encodedResourceName) { const segments = Math.ceil(totalSeconds / CONFIG.segmentSize); let remainingSeconds = totalSeconds; let submittedLength = 0; for (let segment = 1; segment <= segments; segment++) { if (!isRunning) break; const isLastSegment = segment === segments; const segmentStudyTime = isLastSegment ? Math.max(remainingSeconds, CONFIG.minSegmentSize) : CONFIG.segmentSize; submittedLength += segmentStudyTime; const currentStudyLength = Math.min(submittedLength, totalSeconds); const params = new URLSearchParams({ courseId: CONFIG.coursePacketId, resourceId: course.resourceID, resourceDirectoryId: resourceDirectoryId, resourceLength: totalSeconds, studyLength: currentStudyLength, studyTime: segmentStudyTime, startTime: Date.now() - (totalSeconds * 1000) + ((segment - 1) * CONFIG.segmentSize * 1000), resourceName: encodedResourceName, resourceType: '1', resourceLibId: '3', studyResourceId: course.SYS_DOCUMENTID, token: getToken() }); const url = `https://www.baomi.org.cn/portal/main-api/v2/studyTime/saveCoursePackage.do?${params}`; try { await fetch(url, { headers: getHeaders() }); remainingSeconds -= segmentStudyTime; if (!isLastSegment) await interruptibleWait(1000); } catch (error) { console.error(`分段提交失败`, error); } } if (!isRunning) return { success: false, course: course.name, paused: true }; return { success: true, course: course.name, directory: course.directoryName }; } function updateProgress(current, total, message) { const progressElement = document.getElementById('progressBar'); const progressText = document.getElementById('progressText'); const statusElement = document.getElementById('status'); const percent = total > 0 ? Math.round((current / total) * 100) : 0; if (progressElement && progressText) { progressElement.style.width = percent + '%'; progressText.textContent = `${current}/${total} (${percent}%)`; } if (statusElement) statusElement.textContent = message; } function showFinalResults() { console.log('--- 批量处理完成 ---'); console.log(`成功提交: ${sessionStats.success}`); console.log(`检测跳过: ${sessionStats.skipped}`); console.log(`提交失败: ${sessionStats.failed}`); updateProgress(filteredCourses.length, filteredCourses.length, '全部处理完成!'); updateStatus(`完成: ${sessionStats.success}提交 / ${sessionStats.skipped}跳过 / ${sessionStats.failed}失败`); } async function submitAllCourses() { if (filteredCourses.length === 0) { updateStatus('正在获取目录列表...'); allCourses = await getAllCourses(); if (allCourses.length === 0) { updateStatus('获取课程失败,请刷新重试'); isRunning = false; updateUIState('stopped'); return; } filteredCourses = allCourses; } if (!isRunning) { isRunning = true; updateUIState('running'); } const total = filteredCourses.length; for (let i = currentIndex; i < total; i++) { if (!isRunning) { break; } currentIndex = i; const course = filteredCourses[i]; updateProgress(i, total, `[${i + 1}/${total}] 检测: ${course.name.substring(0, 10)}...`); const isAlreadyFinished = await checkCourseStatus(course); if (isAlreadyFinished) { console.log(`[跳过] 已完成课程: ${course.name}`); sessionStats.skipped++; await interruptibleWait(500); continue; } updateProgress(i, total, `[${i + 1}/${total}] 学习: ${course.name.substring(0, 10)}...`); const result = await submitCourseProgress(course, i, total); if (result.paused) { break; } if (result.success) sessionStats.success++; else sessionStats.failed++; if (i < total - 1 && isRunning) { await interruptibleWait(CONFIG.delayBetweenRequests); } } if (!isRunning) { if (isPaused) { updateStatus(`已暂停 (进度: ${currentIndex + 1}/${total})`); updateUIState('paused'); } else { updateStatus('已停止'); updateUIState('stopped'); } } else { showFinalResults(); isRunning = false; currentIndex = 0; filteredCourses = []; updateUIState('finished'); } } function updateStatus(message) { const statusElement = document.getElementById('status'); if (statusElement) statusElement.textContent = message; } function updateUIState(state) { const startBtn = document.getElementById('startAutoStudy'); const pauseBtn = document.getElementById('pauseAutoStudy'); const stopBtn = document.getElementById('stopAutoStudy'); const startText = startBtn.querySelector('span'); pauseBtn.disabled = false; pauseBtn.style.opacity = '1'; pauseBtn.style.cursor = 'pointer'; pauseBtn.innerHTML = `${SVG_ICONS.pause}暂停`; stopBtn.disabled = false; stopBtn.style.opacity = '1'; stopBtn.style.cursor = 'pointer'; stopBtn.innerHTML = `${SVG_ICONS.stop}停止`; startBtn.style.display = 'none'; pauseBtn.style.display = 'none'; stopBtn.style.display = 'none'; if (state === 'running') { pauseBtn.style.display = 'flex'; stopBtn.style.display = 'flex'; } else if (state === 'paused') { startBtn.style.display = 'flex'; stopBtn.style.display = 'flex'; startText.textContent = '继续学习'; // startIcon 保持 play } else if (state === 'stopped' || state === 'finished') { startBtn.style.display = 'flex'; startText.textContent = '开始智能学习'; } else { // initial startBtn.style.display = 'flex'; } } function addUI() { const existingUI = document.getElementById('auto-study-ui'); if (existingUI) existingUI.remove(); const ui = document.createElement('div'); ui.id = 'auto-study-ui'; ui.innerHTML = `