// ==UserScript== // @name 中国保密在线网-自动学习工具 // @namespace http://tampermonkey.net/ // @version 2.8 // @description 自动完成中国保密在线网的视频学习,智能处理不同时长视频 // @author 卖炸弹的小女孩 // @match https://www.baomi.org.cn/* // @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以便动态更新) let CONFIG = { coursePacketId: null, delayBetweenRequests: 2000, debugMode: true, maxRetries: 3, urlParams: {}, // 新增配置:长视频分段提交设置 longVideoThreshold: 600, // 超过10分钟的视频视为长视频 segmentSize: 300, // 每次提交的学习时长(秒) minSegmentSize: 60 // 最小提交时长(秒) }; // 全局变量 let allCourses = []; let filteredCourses = []; let isRunning = false; let currentProcess = null; let courseDetail = null; // SVG图标 const SVG_ICONS = { play: '', stop: '', check: '', error: '', folder: '', clock: '', settings: '', book: '', time: '', filter: '' }; // 工具函数:将时间字符串转换为秒数 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() { // 从cookie获取 const cookieMatch = document.cookie.match(/token=([^;]+)/); if (cookieMatch) { return cookieMatch[1]; } // 从localStorage获取 if (localStorage.getItem('token')) { return localStorage.getItem('token'); } // 从headers获取 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 getCourseDetail() { const { pubId, siteId } = CONFIG.urlParams; if (!pubId || !siteId) { console.error('缺少必要的URL参数: pubId 或 siteId'); return null; } const url = `https://www.baomi.org.cn/portal/main-api/resource/simpleDetail.do?pubId=${pubId}&siteId=${siteId}`; if (CONFIG.debugMode) { console.log('正在获取课程详情...', url); } try { const response = await fetch(url, { headers: getHeaders() }); const data = await response.json(); if (data.successResult && data.data) { console.log('成功获取课程详情'); return data.data; } else { console.error('获取课程详情失败:', data.error); return null; } } catch (error) { console.error('获取课程详情网络错误:', error); return null; } } // 获取目录列表 async function getDirectoryList() { const url = `https://www.baomi.org.cn/portal/main-api/v2/coursePacket/getCourseDirectoryList?scale=1&coursePacketId=${CONFIG.coursePacketId}`; if (CONFIG.debugMode) { console.log('正在获取目录列表...', url); } try { const response = await fetch(url, { headers: getHeaders() }); const data = await response.json(); if (data.status === 0 && data.data) { console.log('成功获取到目录结构'); return data.data; } else { console.error('获取目录列表失败:', data.message); return []; } } catch (error) { console.error('获取目录列表网络错误:', 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()}`; if (CONFIG.debugMode) { console.log(`正在获取目录"${directoryName}"的课程列表...`); } try { const response = await fetch(url, { headers: getHeaders() }); const data = await response.json(); if (data.status === 0 && data.data && data.data.listdata) { const courses = data.data.listdata.map(course => ({ ...course, directoryName: directoryName, directoryId: directoryId, totalSeconds: timeToSeconds(course.timeLength) // 预计算总秒数 })); if (CONFIG.debugMode) { console.log(`目录"${directoryName}"获取到 ${courses.length} 个课程`); } return courses; } else { console.error(`获取目录"${directoryName}"课程列表失败:`, data.message); return []; } } catch (error) { if (retryCount < CONFIG.maxRetries) { console.log(`第${retryCount + 1}次重试获取目录"${directoryName}"课程列表...`); await new Promise(resolve => setTimeout(resolve, 1000)); return getCoursesByDirectory(directoryId, directoryName, retryCount + 1); } else { console.error(`获取目录"${directoryName}"课程列表网络错误:`, error); return []; } } } // 获取所有课程 async function getAllCourses() { console.log('开始获取所有目录和课程...'); const directoryList = await getDirectoryList(); if (directoryList.length === 0) { console.error('未获取到任何目录'); return []; } const allDirectories = getAllDirectories(directoryList); console.log(`发现 ${allDirectories.length} 个目录`); 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, 500)); } } console.log(`总共获取到 ${allCourses.length} 个课程`); return allCourses; } // 提交单个课程的学习进度 - 智能分段提交 async function submitCourseProgress(course, index, total, retryCount = 0) { const totalSeconds = course.totalSeconds || timeToSeconds(course.timeLength); // 使用课程的SYS_UUID作为resourceDirectoryId const resourceDirectoryId = course.SYS_UUID; // 修复:对resourceName进行双重编码 const encodedResourceName = encodeURIComponent(course.name); // 判断是否为长视频 const isLongVideo = totalSeconds > CONFIG.longVideoThreshold; if (CONFIG.debugMode) { console.log(`正在处理课程 ${index + 1}/${total}: ${course.name}`); console.log(` 视频时长: ${course.timeLength} (${totalSeconds}秒)`); console.log(` 视频类型: ${isLongVideo ? '长视频' : '短视频'}`); } 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}`; if (CONFIG.debugMode) { console.log(` 提交方式: 一次性提交`); console.log(` resourceDirectoryId: ${resourceDirectoryId}`); console.log(` resourceId: ${course.resourceID}`); } try { const response = await fetch(url, { headers: getHeaders() }); const result = await response.json(); if (result.status === 0) { console.log(`✅ 完成: ${course.name}`); return { success: true, course: course.name, directory: course.directoryName }; } else { console.error(`❌ 提交失败: ${course.name} - ${result.message}`); return { success: false, course: course.name, error: result.message, directory: course.directoryName }; } } catch (error) { console.error(`❌ 网络错误: ${course.name} -`, 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; if (CONFIG.debugMode) { console.log(` 提交方式: 分段提交 (${segments}段)`); } // 分段提交 for (let segment = 1; segment <= segments; segment++) { 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}`; if (CONFIG.debugMode) { console.log(` 第${segment}段: studyLength=${currentStudyLength}, studyTime=${segmentStudyTime}`); } try { const response = await fetch(url, { headers: getHeaders() }); const result = await response.json(); if (result.status === 0) { if (CONFIG.debugMode) { console.log(` ✅ 第${segment}段提交成功`); } } else { console.error(` ❌ 第${segment}段提交失败: ${result.message}`); // 继续提交下一段,不立即返回失败 } remainingSeconds -= segmentStudyTime; // 如果不是最后一段,等待一段时间再提交下一段 if (!isLastSegment) { await new Promise(resolve => setTimeout(resolve, 1000)); } } catch (error) { console.error(` ❌ 第${segment}段网络错误:`, error); // 继续提交下一段,不立即返回失败 } } console.log(`✅ 长视频提交完成: ${course.name}`); 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(results) { console.log('批量提交完成!'); console.log('统计结果:'); console.log(` 成功: ${results.success} 个课程`); console.log(` 失败: ${results.failed} 个课程`); console.log(` 跳过: ${results.skipped} 个课程`); console.log(` 总计: ${results.total} 个课程`); const byDirectory = {}; results.details.forEach(result => { const dir = result.directory || '未知目录'; if (!byDirectory[dir]) { byDirectory[dir] = { success: 0, failed: 0, skipped: 0, courses: [] }; } if (result.success) { byDirectory[dir].success++; } else if (result.skipped) { byDirectory[dir].skipped++; } else { byDirectory[dir].failed++; } byDirectory[dir].courses.push(result); }); console.log('按目录统计:'); Object.keys(byDirectory).forEach(dir => { const stats = byDirectory[dir]; console.log(` ${dir}: ✅${stats.success} ❌${stats.failed} ⏭️${stats.skipped}`); }); if (results.failed > 0) { console.log('失败课程详情:'); results.details.filter(r => !r.success && !r.skipped).forEach((fail, index) => { console.log(` ${index + 1}. [${fail.directory}] ${fail.course} - ${fail.error}`); }); } updateProgress(results.total, results.total, '提交完成!'); updateStatus(`完成: ${results.success}成功/${results.failed}失败/${results.skipped}跳过`); } // 批量提交所有课程 async function submitAllCourses() { if (isRunning) { console.log('提交正在进行中,请勿重复点击'); return; } isRunning = true; console.log('🚀 开始自动提交课程学习进度...'); try { // 获取所有课程 updateStatus('正在获取目录列表...'); updateProgress(0, 0, '初始化...'); allCourses = await getAllCourses(); if (allCourses.length === 0) { console.error('❌ 未获取到任何课程,请检查网络连接和登录状态'); isRunning = false; updateStatus('获取课程失败,请检查登录状态'); return; } filteredCourses = allCourses; if (filteredCourses.length === 0) { console.log('🎉 所有课程都已学习完成,无需提交!'); isRunning = false; updateStatus('所有课程都已学习完成!'); updateProgress(1, 1, '已完成'); return; } console.log(`📋 开始提交 ${filteredCourses.length} 个未完成课程`); updateStatus(`开始提交 ${filteredCourses.length} 个未完成课程...`); // 统计结果 const results = { total: allCourses.length, success: 0, failed: 0, skipped: allCourses.length - filteredCourses.length, details: [] }; // 逐个提交未完成课程进度 for (let i = 0; i < filteredCourses.length; i++) { if (!isRunning) { console.log('⏹️ 用户手动停止'); break; } const course = filteredCourses[i]; updateProgress(i, filteredCourses.length, `正在提交: ${course.name.substring(0, 20)}...`); const result = await submitCourseProgress(course, i, filteredCourses.length); results.details.push(result); if (result.success) { results.success++; } else { results.failed++; } // 增加延迟,避免请求过于频繁 if (i < filteredCourses.length - 1 && isRunning) { await new Promise(resolve => setTimeout(resolve, CONFIG.delayBetweenRequests)); } } // 显示最终结果 showFinalResults(results); isRunning = false; return results; } catch (error) { console.error('❌ 提交过程发生错误:', error); isRunning = false; updateStatus('提交过程发生错误: ' + error.message); } } // 更新状态 function updateStatus(message) { const statusElement = document.getElementById('status'); if (statusElement) { statusElement.textContent = message; } } // 重新设计的UI面板 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 = `