// ==UserScript== // @name 国家中小学智慧教育平台 - 培训 // @namespace http://tampermonkey.net/ // @version 18.0 // @description 注入完成所有视频进度 // @author You // @match https://basic.smartedu.cn/* // @match https://www.smartedu.cn/* // @icon https://basic.smartedu.cn/favicon.ico // @grant none // @run-at document-start // ==/UserScript== (function() { 'use strict'; function log(...args) { console.log('[注入完成]', ...args); } const params = new URLSearchParams(location.search); const resourceId = params.get('resourceId'); const url = location.href; const isCoursePage = url.includes('courseDetail') || url.includes('courseIndex') || url.includes('resourceId=') || url.includes('/jiaoshi/'); if (!isCoursePage) return; log('v18.0 启动: ' + (resourceId || '').substring(0, 8)); /* ===== 课程结构 ===== */ let courseStructure = null; let allResourceIds = []; let allActivityIds = []; let asyncUrl = null; /* ===== 拦截 XHR ===== */ const _open = XMLHttpRequest.prototype.open; const _send = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.open = function(method, u, ...rest) { this._url = u; this._method = method; return _open.call(this, method, u, ...rest); }; XMLHttpRequest.prototype.send = function(body) { const self = this; /* ===== 1. 拦截 async_begin ===== */ if (/progress\/actions\/async_begin/.test(self._url || '')) { asyncUrl = self._url; log('捕获 async_begin'); self.addEventListener('readystatechange', function() { if (self.readyState === 4 && self.status === 200) { try { const data = JSON.parse(self.responseText); if (data.ext_info) { const ext = typeof data.ext_info === 'string' ? JSON.parse(data.ext_info) : data.ext_info; courseStructure = ext; allResourceIds = Object.keys(ext.resource_progress || {}); allActivityIds = Object.keys(ext.activity_progress || {}); log('课程结构: 资源=' + allResourceIds.length + ' 活动=' + allActivityIds.length); setTimeout(submitComplete, 1000); } } catch(e) { log('解析async_begin失败: ' + e.message); } } }); return _send.call(this, body); } /* ===== 2. 拦截 async(修改为全部完成) ===== */ if (/progress\/actions\/async(?!_begin)/.test(self._url || '') && body && typeof body === 'string') { asyncUrl = self._url; log('捕获 async'); try { const data = JSON.parse(body); const ext = typeof data.ext_info === 'string' ? JSON.parse(data.ext_info) : data.ext_info; /* 从请求体中提取课程结构 */ if (!courseStructure && ext.resource_progress) { courseStructure = ext; allResourceIds = Object.keys(ext.resource_progress); allActivityIds = Object.keys(ext.activity_progress || {}); log('从async提取结构: 资源=' + allResourceIds.length + ' 活动=' + allActivityIds.length); setTimeout(submitComplete, 1000); } /* 修改所有资源为完成 */ if (ext.resource_progress) { for (const rid in ext.resource_progress) { ext.resource_progress[rid] = 2; } } if (ext.activity_progress) { for (const aid in ext.activity_progress) { ext.activity_progress[aid] = 2; } } if (ext.resource_max_pos) { for (const rid in ext.resource_max_pos) { const cur = ext.resource_max_pos[rid]; if (cur && typeof cur === 'object' && cur.pos < 99999) { cur.pos = 99999; } } } data.ext_info = JSON.stringify(ext); data.progress = 100; data.status = 2; log('已注入 async: 全部→完成'); self.addEventListener('load', function() { log('async响应: ' + self.status + ' ' + self.responseText.substring(0, 80)); }); return _send.call(this, JSON.stringify(data)); } catch(e) { log('注入async出错: ' + e.message); } } /* ===== 3. 拦截 x-study-record-api PUT(位置设为99999) ===== */ if (/x-study-record-api.*resource_learning_positions/.test(self._url || '') && body && typeof body === 'string') { log('捕获 study-record PUT: ' + self._url); try { const data = JSON.parse(body); const oldPos = data.position; data.position = '99999'; log('位置修改: ' + oldPos + ' → 99999'); self.addEventListener('load', function() { log('study-record响应: ' + self.status + ' ' + self.responseText.substring(0, 80)); }); return _send.call(this, JSON.stringify(data)); } catch(e) { log('注入study-record出错: ' + e.message); } } return _send.call(this, body); }; /* ===== 4. 拦截 fetch ===== */ const _fetch = window.fetch; if (_fetch) { window.fetch = function(input, init) { const reqUrl = typeof input === 'string' ? input : (input.url || ''); if (/progress\/actions\/async_begin/.test(reqUrl)) { asyncUrl = reqUrl; return _fetch.call(this, input, init).then(function(resp) { const clone = resp.clone(); clone.text().then(function(text) { try { const data = JSON.parse(text); if (data.ext_info) { const ext = typeof data.ext_info === 'string' ? JSON.parse(data.ext_info) : data.ext_info; courseStructure = ext; allResourceIds = Object.keys(ext.resource_progress || {}); log('fetch课程结构: 资源=' + allResourceIds.length); setTimeout(submitComplete, 1000); } } catch(e) {} }); return resp; }); } if (/progress\/actions\/async(?!_begin)/.test(reqUrl) && init && init.body) { try { const data = JSON.parse(init.body); const ext = typeof data.ext_info === 'string' ? JSON.parse(data.ext_info) : data.ext_info; if (!courseStructure && ext.resource_progress) { courseStructure = ext; allResourceIds = Object.keys(ext.resource_progress); setTimeout(submitComplete, 1000); } if (ext.resource_progress) { for (const rid in ext.resource_progress) ext.resource_progress[rid] = 2; } if (ext.activity_progress) { for (const aid in ext.activity_progress) ext.activity_progress[aid] = 2; } data.ext_info = JSON.stringify(ext); data.progress = 100; data.status = 2; init.body = JSON.stringify(data); log('已注入 fetch async: 全部→完成'); } catch(e) {} } if (/x-study-record-api.*resource_learning_positions/.test(reqUrl) && init && init.body) { try { const data = JSON.parse(init.body); data.position = '99999'; init.body = JSON.stringify(data); log('已注入 fetch study-record: 位置→99999'); } catch(e) {} } return _fetch.call(this, input, init); }; } /* ===== 5. 主动完成请求 ===== */ function submitComplete() { if (!courseStructure || !asyncUrl) { log('submitComplete: 数据不全'); return; } const ext = { resource_progress: {}, resource_max_pos: {}, activity_progress: {}, activity_last_learning_resource: courseStructure.activity_last_learning_resource || {}, last_learning_activity: courseStructure.last_learning_activity || {}, miniwork_progress: {}, activity_exam_progress: {}, activity_event: {}, resource_study_time_ignore: [], additional_params: courseStructure.additional_params || {} }; for (const rid in courseStructure.resource_progress) { ext.resource_progress[rid] = 2; ext.resource_max_pos[rid] = { pos: 99999, type: 'video' }; } for (const aid in courseStructure.activity_progress) { ext.activity_progress[aid] = 2; } const xhr = new XMLHttpRequest(); xhr.open('POST', asyncUrl, true); xhr.setRequestHeader('Content-Type', 'application/json'); xhr.withCredentials = true; xhr.onload = function() { log('主动完成请求: ' + xhr.status + ' ' + xhr.responseText.substring(0, 100)); }; xhr.onerror = function() { log('主动完成请求失败'); }; xhr.send(JSON.stringify({ ext_info: JSON.stringify(ext), progress: 100, status: 2 })); log('已发送主动完成请求'); } log('拦截器已就绪'); })();