// ==UserScript== // @name (秒刷)重庆人社培训网(cqrl.21tb.com)·自动刷课工具免费版 // @namespace https://cqrl.21tb.com/ // @version 2.5.1 // @description 重庆人社培训网(cqrl.21tb.com) 自动刷课工具(半自动)。功能:不真实播放视频,直连平台接口快速完成课程进度并同步完成态。使用方法:进入任意一门课程的「学习/播放页」(地址含 courseStudyItem.learn.do、页面有视频播放器)→ 点面板「⚡ 后台效率刷」即一键完成本课;刷完弹出「恭喜完成」提示。自带双通道(HTTP 直连 + 官方 iframe 通道)、防刷软拦截自动退避重试、诊断/自检/通道测试/刷新率。遇到问题点「💬反馈群」加群交流或「🧾诊断」复制信息反馈。 // @author cqrl-tools // @match https://cqrl.21tb.com/* // @match http://cqrl.21tb.com/* // @run-at document-idle // @noframes // @grant none // @require https://cdn.jsdelivr.net/npm/crypto-js@4.2.0/crypto-js.min.js // @license All Rights Reserved // ==/UserScript== (function () { 'use strict'; const CORP_CODE = 'cqrl'; const API_BASE = 'https://cqrl.21tb.com'; const ANTI_CHEAT_RE = /异常|学霸君|行为|按要求|防刷|strict/i; const VER = '2.5.1'; const FAVICON = 'data:image/x-icon;base64,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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACf0ADgEC8hEA/wQA//4AAAD/AQAAAAAAAAEAAPH4BgAA/gQA/gQGAP4EBgD9AwQA/QMEAAUEAwAECQgAvMrsIwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACj9wxj+//8F//0BAAAA/wAAAP8AAAMBAAD3AADz7gUAAAj+AAD+/wAA//8AAf7+AAH+/gD/AgIA+wEEAPfs6icAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHF/Ma/vv/AAAA/wD//QEAAgABAAIE/QD/+wAA6PUKAAAQ/AD//QIA/wICAAD//wAAAAAAAAICAP8A/wD3BgwiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/QXrGP/9AwAAAAAAAAAA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'; const LS_PANEL = 'cqrl_panel_v2'; const LS_BATCH = 'cqrl_batch_v1'; let stopFlag = false; let BUSY = false; let _resStart = {}; function sleep(ms) { return new Promise(r => setTimeout(r, ms)); } const REQ_TIMEOUT = 15000; function withTimeout(promise, ms, fallback) { let done = false; return new Promise(function (resolve) { const t = setTimeout(function () { if (!done) { done = true; resolve(fallback); } }, ms); Promise.resolve(promise).then(function (v) { if (done) return; done = true; clearTimeout(t); resolve(v); }, function () { if (done) return; done = true; clearTimeout(t); resolve(fallback); }); }); } function getCookie(n) { const m = document.cookie.match(new RegExp('(?:^|; )' + n + '=([^;]*)')); return m ? decodeURIComponent(m[1]) : ''; } function ssGet(k) { try { return (sessionStorage.getItem(k) || ''); } catch (_) { return ''; } } function getElnSessionId() { return (ssGet('eln_session_id') || getCookie('eln_session_id') || (new URLSearchParams(location.search).get('eln_session_id') || '') || (location.hash.match(/eln_session_id=([^&]+)/) || [])[1] || '').trim(); } function getCorpCode() { return ssGet('corp_code') || CORP_CODE; } function getLocalHeader() { const lang = (navigator.language || 'zh').substr(0, 2); return lang === 'en' ? 'en_US' : 'zh_CN'; } async function pageFetch(url, options, ms) { options = options || {}; const limit = Number(ms) || REQ_TIMEOUT; const ctl = (typeof AbortController === 'function') ? new AbortController() : null; let timer = null; try { if (ctl) { options.signal = ctl.signal; timer = setTimeout(function () { try { ctl.abort(); } catch (_) {} }, limit); } const resp = await fetch(url, Object.assign({ credentials: 'include' }, options)); const text = await resp.text(); let data = text; try { data = JSON.parse(text); } catch (_) {} return { ok: resp.ok, status: resp.status, data, raw: text }; } catch (e) { return { ok: false, status: 0, data: null, raw: '', timeout: true, error: String((e && e.message) || e) }; } finally { if (timer) clearTimeout(timer); } } async function httpGet(url) { return pageFetch(url, { method: 'GET', headers: { Accept: 'application/json, text/plain, */*' } }); } async function httpPost(url, body, headers) { const payload = typeof body === 'string' ? body : JSON.stringify(body || {}); const hdrs = Object.assign({ 'Content-Type': 'application/json', Accept: 'application/json, text/plain, */*' }, headers || {}); return pageFetch(url, { method: 'POST', headers: hdrs, body: payload }); } async function httpPostForm(url, formObj) { const payload = typeof formObj === 'string' ? formObj : new URLSearchParams(formObj || {}).toString(); return pageFetch(url, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', Accept: 'application/json, text/plain, */*' }, body: payload, }); } async function rmsPost(path, body) { const resp = await httpPost(API_BASE + path, body || {}); return resp.data; } function unwrap(data) { if (data == null) return data; if (typeof data !== 'object') return data; if (data.bizResult !== undefined) return data.bizResult; if (data.result !== undefined) return data.result; if (data.data !== undefined) return data.data; if (Array.isArray(data.rows)) return data.rows; return data; } function parseJsonData(raw) { if (raw == null) return null; if (typeof raw === 'object') return raw; const text = String(raw).trim(); if (!text || (text[0] !== '{' && text[0] !== '[')) return raw; try { return JSON.parse(text); } catch (_) { return raw; } } function nmsQuery(extra) { const sid = getElnSessionId(); const q = new URLSearchParams(Object.assign({ corpCode: getCorpCode(), eln_session_id: sid }, extra || {})); return q.toString(); } async function nmsGetRaw(path, extra) { const url = API_BASE + path + '?' + nmsQuery(extra); const resp = await httpGet(url); return parseJsonData(resp.data); } async function nmsPostRaw(path, body, extra) { const url = API_BASE + path + '?' + nmsQuery(extra); const resp = await httpPost(url, body || {}); return parseJsonData(resp.data); } function buildBizOimHeaders(apiPath, sessionId) { const clean = apiPath.replace(/^\//, ''); const parts = clean.split('/'); const module = parts[parts.length - 2] || ''; let method = parts[parts.length - 1] || ''; const q = method.indexOf('?'); if (q >= 0) method = method.substring(0, q); const time = Date.now(); const headers = { 'Content-Type': 'application/json', '-local': getLocalHeader() }; if (typeof CryptoJS !== 'undefined' && sessionId) { try { const signKey = CryptoJS.MD5(sessionId).toString(); const plain = 'module=' + module + '&method=' + method + '&time=' + time; const sign = CryptoJS.HmacSHA1(plain, signKey).toString(CryptoJS.enc.Base64); headers['-SID'] = sessionId; headers['-TIME'] = String(time); headers['-SIGN'] = sign; } catch (e) {} } return headers; } let PROVIDER = ''; function getFrameProvider() { try { const f = parsePlayFrame(); if (f && f.provider) return String(f.provider); } catch (_) {} try { const m = (document.documentElement.outerHTML || '').match(/coursePlay\/[0-9a-fA-F]{16,}(?:%26|&)([^%&"'<>\s]{2,24})(?:%26|&)/i); if (m && m[1] && !/^[0-9a-fA-F]{16,}$/.test(m[1])) return decodeURIComponent(m[1]); } catch (_) {} return ''; } function providerList(pref) { const p = String(pref || PROVIDER || getFrameProvider() || 'default'); return p === 'default' ? ['default', CORP_CODE] : [p, 'default']; } function recPayload(learningId, sourceId, extra, provider) { return Object.assign({ courseId: learningId, sourceId: sourceId, providerCorpCode: provider || PROVIDER || 'default' }, extra || {}); } function chapPayload(sourceId, provider) { return { courseId: sourceId, sourceId: sourceId, providerCorpCode: provider || PROVIDER || 'default' }; } function brief(d) { try { const s = (d == null) ? 'null' : (typeof d === 'object' ? JSON.stringify(d) : String(d)); return s.slice(0, 160); } catch (_) { return ''; } } const SUCCESS_CODES = [0, 200, 1001, 2000]; function respCodeOf(data) { if (data == null) return null; if (typeof data === 'object') { for (const k of ['code', 'respCode', 'resultCode', 'status', 'codeValue']) { if (data[k] != null && data[k] !== '') return Number(data[k]); } } return null; } function isWriteOk(data) { if (data == null) return false; if (typeof data === 'string') return /^(true|1|ok|success)$/i.test(data.trim()); const s = JSON.stringify(data); if (!s || s === '{}' || s === '""') return false; if (ANTI_CHEAT_RE.test(s)) return false; const c = respCodeOf(data); if (c != null) return SUCCESS_CODES.indexOf(c) >= 0; return true; } function findRecord(records, resourceId) { return (records || []).find(function (r) { return String(r.resourceId) === String(resourceId); }); } function isResourceDone(records, resourceId) { const r = findRecord(records, resourceId); if (!r) return false; if (r.confirmFinish === 1 || r.confirmFinish === true || r.confirmFinish === '1') return true; const tf = Number(r.timeToFinish || 0); const pos = Number(r.currentPosition || r.position || 0); return tf > 0 && pos >= tf - 1; } function isVideoResource(res) { if (!res) return false; const type = String(res.type || res.resourceType || 'video').toLowerCase(); if (/doc|document|pdf|text|exam|test|html|scorm|ppt|audio|mp3/.test(type)) return false; const dur = Number(res.timeToFinish || res.duration || res.videoDuration || 0); return dur > 0 || type === 'video' || /video|mp4|flv|media/.test(type); } function collectResources(data, keepAll) { const out = []; const seen = {}; const push = function (res, chapterId) { if (!res || typeof res !== 'object') return; const rid = res.resourceId || res.resourceRelId || res.id; if (!rid || seen[rid]) return; seen[rid] = 1; out.push({ chapterId: chapterId || res.chapterId || res.chapterID || res.parentChapterId || '', resourceId: rid, resourceName: res.resourceName || res.name || res.title || rid, type: res.type || res.resourceType || 'video', timeToFinish: Number(res.timeToFinish || res.duration || res.videoDuration || 0) || 1800, }); }; const root = unwrap(data); const chapters = (root && (root.chapters || root.chapterList || root.chapterDTOS)) || (data && (data.chapters || data.chapterList || data.chapterDTOS)); if (Array.isArray(chapters) && chapters.length) { chapters.forEach(function (ch) { if (!ch || typeof ch !== 'object') return; const cid = ch.chapterId || ch.chapterID || ch.id || ''; const arr = ch.resourceDTOS || ch.resources || ch.resourceList || ch.children || ch.resourceDTOList || []; if (Array.isArray(arr)) arr.forEach(function (r) { push(r, cid); }); if (ch.resourceId) push(ch, cid); }); } if (!out.length) { const topArr = (root && (root.resources || root.resourceList || root.resourceDTOS)) || (Array.isArray(root) ? root : null); if (Array.isArray(topArr)) topArr.forEach(function (r) { push(r, ''); }); } if (!out.length) { (function walk(o, ctx) { if (o == null || typeof o !== 'object') return; if (Array.isArray(o)) { o.forEach(function (x) { walk(x, ctx); }); return; } let c = ctx; const cid = o.chapterId || o.chapterID || o.parentChapterId || ''; if (cid) c = cid; if (o.resourceId || o.resourceRelId) push(o, c); for (const k in o) walk(o[k], c); })(data, ''); } if (keepAll) return out; const vids = out.filter(isVideoResource); return vids.length ? vids : out; } async function fetchChapters(sourceId, body) { const data = await rmsPost('/tbc-rms/course/showCourseChapter', body || chapPayload(sourceId)); return collectResources(data); } function chapterShapes(cid) { const pv = providerList(); const out = []; pv.forEach(function (p) { out.push({ courseId: cid, providerCorpCode: p }); out.push({ courseId: cid, sourceId: cid, providerCorpCode: p }); }); return out; } async function tryChapters(cid) { const map = {}; let firstBody = null; for (const body of chapterShapes(cid)) { let rs = []; try { rs = collectResources(await withTimeout(rmsPost('/tbc-rms/course/showCourseChapter', body), REQ_TIMEOUT, null)); } catch (_) {} if (!rs || !rs.length) continue; if (!firstBody) firstBody = body; rs.forEach(function (r) { const old = map[r.resourceId]; if (!old || Number(r.timeToFinish || 0) > Number(old.timeToFinish || 0)) map[r.resourceId] = r; }); } const list = Object.keys(map).map(function (k) { return map[k]; }); if (!list.length) return null; if (firstBody && firstBody.providerCorpCode) PROVIDER = firstBody.providerCorpCode; return { resources: list, body: firstBody }; } async function expandAllResources(bg) { const seen = {}; (bg.resources || []).forEach(function (r) { seen[r.resourceId] = 1; }); let added = 0; const cid = bg.sourceId || bg.courseId; for (const body of chapterShapes(cid)) { let all = []; try { all = collectResources(await withTimeout(rmsPost('/tbc-rms/course/showCourseChapter', body), REQ_TIMEOUT, null), true); } catch (_) {} (all || []).forEach(function (r) { if (!seen[r.resourceId]) { seen[r.resourceId] = 1; bg.resources.push(r); added++; } }); } return added; } async function fetchStudyRecords(learningId, sourceId, pref) { const list0 = providerList(pref); let fallback = []; for (let i = 0; i < list0.length; i++) { const pv = list0[i]; let arr = []; try { const data = await rmsPost('/tbc-rms/record/getStudyRecordList', recPayload(learningId, sourceId, null, pv)); const list = unwrap(data); arr = Array.isArray(list) ? list : ((list && list.recordList) || []); if (!arr.length && Array.isArray(data)) arr = data; } catch (_) {} if (arr.length) { PROVIDER = pv; return arr; } if (!i) fallback = arr; } return fallback; } async function fetchStudyRate(learningId, sourceId, pref) { const list0 = providerList(pref); let best = 0; for (const pv of list0) { let num = 0; try { const data = await rmsPost('/tbc-rms/record/getStudyRate', recPayload(learningId, sourceId, null, pv)); const rate = unwrap(data); num = Number(rate != null ? rate : (data && data.bizResult != null ? data.bizResult : 0)) || 0; } catch (_) {} if (num > best) { best = num; if (num > 0) PROVIDER = pv; } } return best; } async function syncStudyRecord(learningId, sourceId, pref) { let last = null; for (const pv of providerList(pref)) { try { last = await rmsPost('/tbc-rms/record/syncStudyRecord', recPayload(learningId, sourceId, null, pv)); } catch (_) {} } return last; } async function saveStudyLog(courseId) { const sid = getElnSessionId(); const path = '/biz-oim/course/saveStudyLog.do?courseId=' + encodeURIComponent(courseId); return httpPost(API_BASE + path, { courseId, providerCorpCode: CORP_CODE }, buildBizOimHeaders(path, sid)); } async function enterCourseStudy(courseId) { const q = new URLSearchParams({ courseId: String(courseId || ''), studyType: 'STUDY', courseType: 'NEW_COURSE_CENTER' }); return httpGet(API_BASE + '/els/html/studyCourse/studyCourse.enterCourse.do?' + q.toString()); } async function knowStudyRecord(courseId) { return httpPostForm(API_BASE + '/els/html/courseStudyItem/courseStudyItem.knowStudyRecord.do', { courseId: String(courseId || '') }); } async function exitStudyForRms(courseId) { const url = API_BASE + '/els/html/courseStudyItem/courseStudyItem.exitStudyForRms.do?courseId=' + encodeURIComponent(String(courseId || '')); try { return await httpPost(url, ''); } catch (_) { return null; } } async function ensureEntered(courseId, sourceId) { try { await enterCourseStudy(courseId); } catch (_) {} try { await knowStudyRecord(courseId); } catch (_) {} try { await syncStudyRecord(courseId, sourceId); } catch (_) {} try { await saveStudyLog(courseId); } catch (_) {} } async function writeRecordWhileClose(payload) { return rmsPost('/tbc-rms/record/writeRecordWhileClose', payload || {}); } async function updateCourseRecord(payload) { return rmsPost('/tbc-rms/record/updateCourseRecord', payload || {}); } async function pushRecord(payload) { let r1 = null, r2 = null, e1 = '', e2 = ''; try { r1 = await updateCourseRecord(payload); } catch (e) { e1 = String((e && e.message) || e); } try { r2 = await writeRecordWhileClose(payload); } catch (e) { e2 = String((e && e.message) || e); } const ok = isWriteOk(r1) || isWriteOk(r2); return { ok: ok, r1: r1, r2: r2, err: (e1 || e2 || '') }; } function buildForgePayload(bg, res, rec) { const timeToFinish = Number(rec ? rec.timeToFinish : 0) || Number(res.timeToFinish) || 1800; const currentPosition = Math.max( Number(rec ? rec.currentPosition : 0), Number((bg.positions && bg.positions[res.resourceId]) || 0), timeToFinish ) + 5; const t0 = _resStart[res.resourceId] || (_resStart[res.resourceId] = Date.now()); const elapsed = Math.max(0, Math.floor((Date.now() - t0) / 1000)); const base = Number(rec ? rec.currentStudyTime : 0) || 0; const currentStudyTime = Math.max(10, Math.min(currentPosition, Math.round(Math.max(base, elapsed) * 0.7 + currentPosition * 0.05))); return { recordId: rec && rec.recordId ? rec.recordId : null, courseId: bg.courseId, sourceId: bg.sourceId, providerCorpCode: (bg && bg.provider) || PROVIDER || 'default', chapterId: res.chapterId || (rec ? rec.chapterId : '') || '', resourceId: res.resourceId, timeToFinish, currentPosition, type: res.type || 'video', currentStudyTime: currentStudyTime, confirmFinish: 1, pageIndex: 0, }; } async function syncElsCourseStatusOnce(learningId, sourceId, stageId) { if (stageId) { try { await nmsPostRaw('/nms/html/courseStudy/checkUserScoInitComplete.do', { courseId: learningId, currentStageId: stageId }); } catch (_) {} } try { await enterCourseStudy(learningId); } catch (_) {} try { await knowStudyRecord(learningId); } catch (_) {} try { await syncStudyRecord(learningId, sourceId); } catch (_) {} try { await saveStudyLog(learningId); } catch (_) {} } async function finalizeCoursePlatformStatus(bg) { if (!bg || !bg.courseId) return; const learningId = bg.courseId, sourceId = bg.sourceId; const stageId = String((bg.projectCtx && bg.projectCtx.stageId) || '').trim(); L('↻ 《' + (bg.courseName || '课程') + '》同步平台完成状态…'); await syncElsCourseStatusOnce(learningId, sourceId, stageId); await sleep(800); await syncElsCourseStatusOnce(learningId, sourceId, stageId); const lastRes = bg.resources[bg.resources.length - 1]; const closePayload = buildForgePayload(bg, lastRes, findRecord(bg.records, (lastRes || {}).resourceId)); if (closePayload && closePayload.resourceId) { try { await pushRecord(closePayload); } catch (_) {} await sleep(400); try { await pushRecord(closePayload); } catch (_) {} } try { const uid = bg.userId || getUserId(); if (uid) { const pm = await postMsgRecordWhileClose(learningId, uid, 3500); L(' · iframe 官方通道:' + (pm.ok ? '回执 OK' : pm.reason)); } } catch (_) {} try { await exitStudyForRms(learningId); } catch (_) {} try { await exitStudyForRms(learningId); } catch (_) {} try { await syncStudyRecord(learningId, sourceId, bg.provider); } catch (_) {} let kn = null; try { kn = await knowStudyRecordEx(learningId); } catch (_) {} if (kn && kn.rate > 0) { L(' · 页面完成率(knowStudyRecord)=' + kn.rate + '%' + (kn.stepToGetScore ? ' · 下一步=' + kn.stepToGetScore : '') + (kn.rmsNext === '1' ? ' · 可进入下一步' : '')); } try { bg.studyRate = await fetchStudyRate(learningId, sourceId, bg.provider); } catch (_) {} if (kn && kn.rate > 0) bg.studyRate = Math.max(Number(bg.studyRate || 0), kn.rate); bg.kn = kn; } function getResourcePosition(bg, res) { const rec = findRecord(bg.records, res.resourceId); return Number((bg.positions && bg.positions[res.resourceId]) != null ? bg.positions[res.resourceId] : (rec ? rec.currentPosition : 0)); } function syncProgressFromRecords(bg) { bg.positions = bg.positions || {}; (bg.records || []).forEach(function (rec) { if (!rec || !rec.resourceId) return; bg.positions[rec.resourceId] = Math.max(Number(rec.currentPosition || 0), Number(bg.positions[rec.resourceId] || 0)); }); } async function refreshBgProgress(bg) { if (!bg || !bg.courseId) return bg; bg.records = await fetchStudyRecords(bg.courseId, bg.sourceId, bg.provider); bg.studyRate = await fetchStudyRate(bg.courseId, bg.sourceId, bg.provider); syncProgressFromRecords(bg); return bg; } async function prepareCourse(learningId, sourceId, courseName, chapterBody) { await syncStudyRecord(learningId, sourceId); try { await saveStudyLog(learningId); } catch (_) {} const resources = await fetchChapters(sourceId, chapterBody); const records = await fetchStudyRecords(learningId, sourceId); const studyRate = await fetchStudyRate(learningId, sourceId); if (!resources.length) throw new Error('该课程无视频章节(请从项目阶段里选择具体课程,不要选培训项目名称)'); const bg = { courseId: learningId, sourceId: sourceId, courseName: courseName || learningId, resources, records, studyRate, resIndex: 0, positions: {}, phase: 'study', projectCtx: loadProjectCtx(), provider: (chapterBody && chapterBody.providerCorpCode) || PROVIDER || 'default', }; PROVIDER = bg.provider; if (!records.length) { L('· 首次进入:未查到学习记录,先走官方初始化(enterCourse + 同步记录)…'); await syncElsCourseStatusOnce(learningId, sourceId, String((bg.projectCtx && bg.projectCtx.stageId) || '').trim()); bg.records = await fetchStudyRecords(learningId, sourceId, bg.provider); } await refreshBgProgress(bg); return bg; } function inlineScriptText() { let s = ''; try { document.querySelectorAll('script').forEach(function (sc) { s += '\n' + (sc.textContent || ''); }); } catch (_) {} return s; } function pickVar(src, names) { for (const n of names) { const re = new RegExp('(?:var|let|const|window\\.)\\s*' + n + '\\s*=\\s*[\x27"]([^\x27"]+)[\x27"]', 'i'); const m = src.match(re); if (m && m[1]) return m[1].trim(); } return ''; } let _inlineCache = null; function scanInlineVars(force) { if (_inlineCache && !force) return _inlineCache; let html = ''; try { html = document.documentElement.outerHTML || ''; } catch (_) {} const src = inlineScriptText() + '\n' + html; const v = { courseId: pickVar(src, ['rmsCourseId', 'courseId']) || getUrlCourseId(), userId: pickVar(src, ['userId', 'userid', 'currentUserId']) || '', corpCode: pickVar(src, ['corpCode']) || CORP_CODE, courseTitle: pickVar(src, ['courseTitle']) || (document.title || '').trim(), stepRate: pickVar(src, ['stepRate']) || '', elsSign: (src.match(/elsSign=([^&\x27"\s]+)/) || [])[1] || '', elnSessionId: (src.match(/eln_session_id=([^&\x27"\s]+)/) || [])[1] || getElnSessionId(), }; if (v.courseTitle) v.courseTitle = v.courseTitle.replace(/\s+/g, ' ').trim(); _inlineCache = v; return v; } function getUserId() { const v = scanInlineVars(); return v.userId || (function () { const m = (document.documentElement.outerHTML || '').match(/userId[\x27"]?\s*[:=]\s*[\x27"]([0-9a-fA-F]{16,})[\x27"]/); return m ? m[1] : ''; })(); } function postMsgRecordWhileClose(courseId, userId, waitMs) { return new Promise(function (resolve) { const fr = document.getElementById('aliPlayerFrame') || document.querySelector('iframe.url-course-content'); if (!fr || !fr.contentWindow) { resolve({ ok: false, reason: '未找到 #aliPlayerFrame(需在课程学习/播放页)' }); return; } if (!courseId || !userId) { resolve({ ok: false, reason: '缺少 courseId 或 userId' }); return; } const data = JSON.stringify({ action: 'studyRecordWhileClose', courseId: String(courseId), userId: String(userId) }); let done = false; function finish(r) { if (done) return; done = true; window.removeEventListener('message', onMsg); clearTimeout(timer); resolve(r); } function onMsg(res) { try { let d = res && res.data; if (typeof d === 'string') { try { d = JSON.parse(d); } catch (_) { return; } } if (d && String(d.courseId) === String(courseId) && String(d.userId) === String(userId)) finish({ ok: true, data: d }); } catch (_) {} } const timer = setTimeout(function () { finish({ ok: false, reason: 'iframe 未回执(超时 ' + (waitMs || 3000) + 'ms)' }); }, waitMs || 3000); window.addEventListener('message', onMsg); try { fr.contentWindow.postMessage(data, '*'); console.log('[cqrl] → iframe postMessage: ' + data); } catch (e) { finish({ ok: false, reason: String((e && e.message) || e) }); } }); } async function knowStudyRecordEx(courseId) { let raw = null; try { raw = await knowStudyRecord(courseId); } catch (_) { raw = null; } const d = (raw && raw.data !== undefined) ? raw.data : raw; const rate = Number((d && d.currentStepRate != null) ? d.currentStepRate : (d && d.stepRate) || 0) || 0; return { raw: d, rate: rate, stepToGetScore: (d && d.stepToGetScore) || '', rmsNext: (d && d.rmsNext) || '', score: (d && d.rmsScore) || '', isDone: rate >= 99.5, }; } function readDomId(names) { for (const n of names) { const inp = document.querySelector('input[name="' + n + '"],input[id="' + n + '"]'); if (inp && inp.value) return String(inp.value).trim(); const meta = document.querySelector('meta[name="' + n + '"]'); if (meta && meta.content) return String(meta.content).trim(); try { if (window[n] != null) return String(window[n]); } catch (e) {} } return ''; } function getUrlCourseId() { const m = location.href.match(/courseId=([^&]+)/i); return m ? decodeURIComponent(m[1]) : ''; } function digField(obj, key) { if (obj == null || typeof obj !== 'object') return ''; if (Array.isArray(obj)) { for (const x of obj) { const r = digField(x, key); if (r) return r; } return ''; } if (obj[key] != null && String(obj[key]).length >= 6) return String(obj[key]); for (const k in obj) { if (obj[k] && typeof obj[k] === 'object') { const r = digField(obj[k], key); if (r) return r; } } return ''; } function parsePlayFrame() { const out = { learningId: '', provider: '', sourceId: '', raw: '' }; let frames = []; const main = document.getElementById('aliPlayerFrame') || document.querySelector('iframe.url-course-content'); if (main) frames.push(main); try { document.querySelectorAll('iframe').forEach(function (f) { if (frames.indexOf(f) < 0) frames.push(f); }); } catch (_) {} for (const fr of frames) { const raw = String(fr.getAttribute('src') || fr.src || ''); if (!/coursePlay\//i.test(raw)) continue; const m = raw.match(/coursePlay\/([^/?#"']+)/i); if (!m) continue; const seg = m[1]; let parts = []; try { parts = decodeURIComponent(seg).split(/[&?]/).filter(Boolean); } catch (_) {} if (parts.length < 3) parts = seg.split(/%26|&/).filter(Boolean); if (!out.learningId && parts[0]) { out.learningId = parts[0]; out.raw = decodeURIComponent(seg); } if (!out.provider && parts[1]) out.provider = parts[1]; if (!out.sourceId && parts[2]) out.sourceId = parts[2]; if (out.sourceId) break; } return out; } function collectCourseCandidates() { const cands = []; const push = function (id) { if (id && /^[0-9a-fA-F]{16,}$/.test(id) && cands.indexOf(id) < 0) cands.push(id); }; const inline = scanInlineVars(true); const learningId = getUrlCourseId() || inline.courseId || ''; const frame = parsePlayFrame(); if (frame.sourceId) push(frame.sourceId); push(learningId); if (frame.learningId) push(frame.learningId); for (const n of ['sourceId', 'source_course_id', 'courseSourceId', 'source_id']) { const v = readDomId([n]); if (v) push(v); } const html = document.documentElement.outerHTML; let mm; const re1 = /(?:courseId|sourceId|sourceCourseId|source_course_id)=([0-9a-fA-F]{16,})/gi; while ((mm = re1.exec(html))) push(mm[1]); const re2 = /"(?:courseId|sourceId|sourceCourseId)"\s*:\s*"([0-9a-fA-F]{16,})"/gi; while ((mm = re2.exec(html))) push(mm[1]); const re3 = /coursePlay\/([0-9a-fA-F]{16,})(?:%26[^%"']+)?%26([0-9a-fA-F]{16,})/gi; while ((mm = re3.exec(html))) { push(mm[1]); push(mm[2]); } return { learningId: learningId || frame.learningId || '', frame: frame, cands: cands, userId: inline.userId, courseTitle: inline.courseTitle, stepRate: inline.stepRate, elsSign: inline.elsSign, session: inline.elnSessionId, }; } function getHashQuery(key) { let v = ''; try { v = new URLSearchParams(location.search).get(key) || ''; } catch (_) {} if (v) return v; const m = (location.hash || '').match(new RegExp('[?&]' + key + '=([^&#]+)')); return m ? decodeURIComponent(m[1]) : ''; } async function courseStudyGet(path, params) { const q = new URLSearchParams(Object.assign({ corpCode: getCorpCode(), eln_session_id: getElnSessionId() }, params || {})); const r = await httpGet(API_BASE + '/nms/html/courseStudy/' + path + '?' + q.toString()); return parseJsonData(r.data); } function toRows(d) { const o = unwrap(d); if (Array.isArray(o)) return o; if (o && Array.isArray(o.rows)) return o.rows; if (o && Array.isArray(o.list)) return o.list; if (o && Array.isArray(o.data)) return o.data; return []; } function rateOf(v) { if (v == null || v === '') return 0; if (typeof v === 'number') return v; const s = String(v).trim(); if (/已完成|已通过|done/i.test(s)) return 100; const m = s.match(/(\d+(?:\.\d+)?)/); return m ? Number(m[1]) : 0; } function rowToCourse(row, stageId, ct) { const ci = row.courseInfo || row; const cid = ci.courseId || row.courseId || ''; if (!cid) return null; return { courseId: cid, title: ci.courseTitle || row.courseTitle || '', rate: rateOf(row.currentStepRate != null ? row.currentStepRate : row.currentRate), stageId: stageId, courseType: ct, }; } async function discoverCourseId() { const info = collectCourseCandidates(); UI.setCourse(info.courseTitle || ''); L('· 课程《' + (info.courseTitle || '未知') + '》' + (info.stepRate !== '' ? ' 页面完成率 ' + info.stepRate + '%' : '') + (info.userId ? ' · userId ' + info.userId.slice(0, 8) + '…' : '')); L('· 播放 iframe:' + (info.frame.raw ? info.frame.raw.slice(0, 90) : '(未找到 coursePlay iframe)')); L('· 学习课 ' + (info.learningId || '无') + (info.frame.sourceId ? ' / iframe 解析源课 ' + info.frame.sourceId : '')); L('· 候选 id(' + info.cands.length + '):' + info.cands.slice(0, 8).join(', ') + (info.cands.length > 8 ? '…' : '')); for (const sid of info.cands) { const hit = await tryChapters(sid); if (hit) { L('✓ 命中源课 ' + sid + '(' + hit.resources.length + ' 个视频,请求体 ' + JSON.stringify(hit.body) + ')'); return { learningId: info.learningId || sid, sourceId: sid, chapterBody: hit.body, resources: hit.resources, courseTitle: info.courseTitle, userId: info.userId, stepRate: info.stepRate }; } } if (info.learningId) { try { const rd = await fetchStudyRecords(info.learningId, info.learningId); const sid = digField(rd, 'sourceId') || ''; if (sid && sid !== info.learningId) { const hit = await tryChapters(sid); if (hit) { L('✓ 由学习记录发现源课 ' + sid + '(' + hit.resources.length + ' 个视频)'); return { learningId: info.learningId, sourceId: sid, chapterBody: hit.body, resources: hit.resources, courseTitle: info.courseTitle, userId: info.userId, stepRate: info.stepRate }; } } } catch (_) {} const self = await tryChapters(info.learningId); if (self) { L('✓ 学习课自身即章节源(' + self.resources.length + ' 个视频)'); return { learningId: info.learningId, sourceId: info.learningId, chapterBody: self.body, resources: self.resources, courseTitle: info.courseTitle, userId: info.userId, stepRate: info.stepRate }; } } L('✗ 所有候选均未能返回视频章节。请确认本页是「具体课程的学习/播放页」(含视频播放器的页面),再点「⚡ 后台效率刷」。'); return null; } async function refetchProgress(bg) { try { bg.records = await fetchStudyRecords(bg.courseId, bg.sourceId, bg.provider); } catch (_) {} syncProgressFromRecords(bg); } async function gradualWrite(bg, res, rec) { const end = Math.max(Number(res.timeToFinish || 0), Number(rec ? rec.timeToFinish : 0) || 0) || 1800; const start = Math.max(Number(rec ? rec.currentPosition : 0), 0); const steps = 8; let lastFresh = rec; for (let s = 1; s <= steps; s++) { if (stopFlag) break; let pos = Math.round(start + (end - start) * (s / steps)); if (s === steps) pos = end; const fresh = findRecord(bg.records, res.resourceId) || lastFresh; lastFresh = fresh; const p = buildForgePayload(bg, res, fresh); p.currentPosition = pos; if (s === steps) { p.currentStudyTime = Math.max(p.currentStudyTime, Math.round(end * 0.9)); p.confirmFinish = 1; } else { p.confirmFinish = 0; } try { await pushRecord(p); } catch (_) {} await sleep(1200); if (s % 2 === 0) await refetchProgress(bg); } return true; } async function postMsgChannel(bg, waitMs) { const uid = bg.userId || getUserId(); if (!uid) { L(' · 未取到 userId,跳过 iframe 通道'); return false; } try { const r = await postMsgRecordWhileClose(bg.courseId, uid, waitMs || 2000); if (r.ok) { L(' ✓ iframe 官方通道回执 OK(studyRecordWhileClose)'); return true; } L(' · iframe 通道:' + r.reason); return false; } catch (e) { L(' · iframe 通道异常:' + String((e && e.message) || e)); return false; } } function resUndone(bg, res) { if (!res) return false; return !isResourceDone(bg.records, res.resourceId); } function dumpRecords(bg) { try { (bg.resources || []).forEach(function (res, i) { const rec = findRecord(bg.records, res.resourceId); L(' [' + (i + 1) + '] ' + String(res.resourceName || '').slice(0, 22) + ' type=' + res.type + ' tf=' + res.timeToFinish + ' | ' + (rec ? ('recordId=' + rec.recordId + ' confirm=' + rec.confirmFinish + ' pos=' + rec.currentPosition + '/' + rec.timeToFinish) : '无学习记录')); }); } catch (_) {} } async function brushOneResource(bg, i) { const res = bg.resources[i]; const tag = ' [' + (i + 1) + '/' + bg.resources.length + ']'; L(tag + ' 写入:' + res.resourceName + '(' + res.timeToFinish + 's)'); if (!findRecord(bg.records, res.resourceId)) { L(' · 无学习记录 → 初始化(进入课程)后再写入…'); try { await ensureEntered(bg.courseId, bg.sourceId); } catch (_) {} await refetchProgress(bg); } let rec = findRecord(bg.records, res.resourceId); const r = await withTimeout(pushRecord(buildForgePayload(bg, res, rec)), REQ_TIMEOUT, { ok: false, r1: 'timeout', r2: 'timeout' }); const anti = (r.r1 && ANTI_CHEAT_RE.test(JSON.stringify(r.r1))) || (r.r2 && ANTI_CHEAT_RE.test(JSON.stringify(r.r2))); if (!r.ok) L(' · 直写回执:' + brief(r.r1) + ' | ' + brief(r.r2)); if (anti) L(' · ⚠ 命中平台防刷(检测到异常学习行为):直写可能被忽略,将改走渐进 + 官方 iframe 通道'); await refetchProgress(bg); if (resUndone(bg, res)) { L(' · 渐进式多次上报…'); try { await withTimeout(gradualWrite(bg, res, findRecord(bg.records, res.resourceId)), 65000, null); } catch (_) {} await refetchProgress(bg); } if (resUndone(bg, res)) { const ok = await postMsgChannel(bg, 3500); if (ok) await refetchProgress(bg); if (resUndone(bg, res)) { L(' · 官方通道未生效,重新进入课程后再次提交…'); try { await ensureEntered(bg.courseId, bg.sourceId); } catch (_) {} await refetchProgress(bg); if (resUndone(bg, res)) { try { await withTimeout(gradualWrite(bg, res, findRecord(bg.records, res.resourceId)), 65000, null); } catch (_) {} await refetchProgress(bg); } } } if (resUndone(bg, res) && anti) { for (let t = 1; t <= 2 && resUndone(bg, res) && !stopFlag; t++) { const wait = 4000 * t; L(' · 防刷软拦截,退避 ' + (wait / 1000) + 's 后重试官方 iframe 通道(第 ' + t + ' 次)…'); await sleep(wait); try { await ensureEntered(bg.courseId, bg.sourceId); } catch (_) {} await refetchProgress(bg); await postMsgChannel(bg, 4000); await refetchProgress(bg); } } const done = !resUndone(bg, res); L(done ? (tag + ' ✓ 已完成:' + res.resourceName) : (tag + ' ⚠ 本轮未生效:' + res.resourceName)); return done; } async function brushCourse(disc, courseName, opts) { opts = opts || {}; const maxAttempts = opts.attempts || 3; const deadline = Date.now() + Number(opts.budgetMs || 150000); const bg = await prepareCourse(disc.learningId, disc.sourceId, courseName || disc.courseTitle || '', disc.chapterBody); bg.userId = disc.userId || getUserId() || ''; if (disc.stageId) bg.projectCtx = Object.assign({}, bg.projectCtx || {}, { stageId: disc.stageId }); if (bg.userId) L('· userId ' + bg.userId.slice(0, 8) + '…(官方 iframe 通道可用)'); let rate = Number(bg.studyRate || 0); const undoneCnt = bg.resources.filter(function (r) { return resUndone(bg, r); }).length; L('· 《' + (bg.courseName || '课程') + '》共 ' + bg.resources.length + ' 个视频,未完成 ' + undoneCnt + ' 个,平台完成率 ' + rate + '%'); UI.setStats(bg.resources.length - undoneCnt, bg.resources.length); UI.setRealRate(rate); if (rate >= 99.5) { L(' ✓ 平台已判定 100% 完成,跳过。'); return { ok: true, rate: rate, name: bg.courseName || '课程', bg: bg }; } let best = rate, noGain = 0; for (let attempt = 1; attempt <= maxAttempts; attempt++) { if (stopFlag) break; if (Date.now() > deadline) { L(' ⏱ 本门课时限到,先处理下一门(稍后可重跑补齐)。'); break; } L(' ── 第 ' + attempt + ' 轮 ──'); let targets = []; for (let i = 0; i < bg.resources.length; i++) if (resUndone(bg, bg.resources[i])) targets.push(i); if (!targets.length) { L(' ⚠ 记录显示 ' + bg.resources.length + ' 个视频都已完成,但平台完成率仍为 ' + best + '% → 按平台为准,强制重推全部视频'); const added = await expandAllResources(bg); if (added) L(' · 补齐被过滤的非视频资源 +' + added + ' 项(文档/测验等也可能计入完成率),共 ' + bg.resources.length + ' 项'); dumpRecords(bg); const alt = await withTimeout(fetchStudyRate(bg.courseId, bg.sourceId, null), REQ_TIMEOUT, 0); if (Number(alt || 0) >= 99.5) { best = Number(alt); L(' ✓ 备用通道重读完成率=' + best + '%,判定已完成。'); break; } targets = bg.resources.map(function (_, i) { return i; }); } let firstTarget = true; for (const i of targets) { if (stopFlag || Date.now() > deadline) break; UI.setRealRate(best); if (!firstTarget) await sleep(1200 + Math.floor(Math.random() * 1500)); firstTarget = false; await withTimeout(brushOneResource(bg, i), 90000, false); UI.setStats(bg.resources.filter(function (r) { return !resUndone(bg, r); }).length, bg.resources.length); } await withTimeout(finalizeCoursePlatformStatus(bg), 40000, null); const nr = await withTimeout(fetchStudyRate(bg.courseId, bg.sourceId, bg.provider), REQ_TIMEOUT, best); const newRate = Math.max(Number(nr || 0), 0); bg.studyRate = newRate; UI.setRealRate(newRate); L(' ↻ 第 ' + attempt + ' 轮结束:完成率 ' + best + '% → ' + newRate + '%'); if (newRate >= 99.5) { best = newRate; break; } if (newRate > best) { best = newRate; noGain = 0; } else { noGain++; L(' · 本轮无提升(' + noGain + ' 次),尝试重新进入课程 + 官方 iframe 通道后重试…'); try { await ensureEntered(bg.courseId, bg.sourceId); } catch (_) {} await refetchProgress(bg); try { await postMsgChannel(bg, 3500); } catch (_) {} bg.provider = (bg.provider === CORP_CODE) ? 'default' : CORP_CODE; PROVIDER = bg.provider; try { await syncStudyRecord(bg.courseId, bg.sourceId, bg.provider); } catch (_) {} await refetchProgress(bg); if (noGain >= 2) { L(' ⏹ 连续 ' + noGain + ' 轮无提升,判定本门课暂时无法推进(多为平台防刷拦截),先跳过(不卡住队列)。'); break; } } await refetchProgress(bg); if (attempt < maxAttempts) await sleep(1200); } return { ok: best >= 99.5, rate: best, name: bg.courseName || '课程', bg: bg }; } async function startBrush() { if (BUSY) { L('⚠ 正在刷课中,请先点「■ 停止」。'); return null; } stopFlag = false; try { scanInlineVars(true); } catch (_) {} BUSY = true; UI.setStatus('刷课中', '#22d3ee'); UI.setStats(0, 0); try { const disc = await discoverCourseId(); if (!disc) { L('→ 请在「具体课程的学习/播放页」点击本按钮;或点「🧾 诊断」把信息发我,我据页面结构补充识别。'); UI.setStatus('识别失败', '#fca5a5'); BUSY = false; return { ok: false, rate: 0, name: '识别失败' }; } L('· 学习课 ' + disc.learningId + ' / 源课 ' + disc.sourceId); const r = await brushCourse(disc, '', { attempts: 3, budgetMs: 180000 }); const rate = r.rate, name = r.name; L(rate >= 99.5 ? '✅ 《' + name + '》已完成:getStudyRate=' + rate + '%' : '⚠ 《' + name + '》完成率 ' + rate + '%(未达 100%)。可再点一次「⚡ 后台效率刷」;若反复无效请把「🧾 诊断」内容发我。'); UI.setStatus(rate >= 99.5 ? '完成' : '未完成', rate >= 99.5 ? '#22c55e' : '#fca5a5'); if (rate >= 99.5) { try { showCompletionModal(name, rate, extractCredit(name)); } catch (_) {} } return { ok: rate >= 99.5, rate: rate, name: name }; } catch (e) { L('✗ 异常:' + (e.message || e)); UI.setStatus('异常', '#fca5a5'); return { ok: false, rate: 0, name: '异常' }; } finally { BUSY = false; } } function L(msg) { UI.log(msg); try { console.log('[cqrl] ' + msg); } catch (_) {} } const UI = { root: null, logEl: null, statusEl: null, statsEl: null, rateEl: null, courseEl: null, log(m) { if (!this.logEl) return; const d = document.createElement('div'); d.textContent = m; this.logEl.appendChild(d); while (this.logEl.childNodes.length > 500) this.logEl.removeChild(this.logEl.firstChild); this.logEl.scrollTop = this.logEl.scrollHeight; }, setStatus(s, c) { if (this.statusEl) { this.statusEl.textContent = s; this.statusEl.style.color = c || '#0f172a'; } }, setStats(d, t) { if (this.statsEl) this.statsEl.textContent = d + '/' + t; }, setRealRate(r) { if (this.rateEl) this.rateEl.textContent = (r != null ? r : 0) + '%'; }, setCourse(name) { if (this.courseEl) { this.courseEl.textContent = name || '—'; this.courseEl.title = name || ''; } }, copyLog() { const txt = this.logEl ? this.logEl.innerText : ''; try { navigator.clipboard.writeText(txt); L('📋 日志已复制到剪贴板'); } catch (_) { L('📋 复制失败,请手动选择日志文本'); } }, }; function loadProjectCtx() { try { return JSON.parse(localStorage.getItem('cqrl_project_ctx') || '{}') || {}; } catch (_) { return {}; } } function makeDraggable(root, handle, onEnd) { let sx = 0, sy = 0, ox = 0, oy = 0, dragging = false; handle.style.cursor = 'move'; handle.addEventListener('mousedown', function (e) { dragging = true; const rect = root.getBoundingClientRect(); ox = rect.left; oy = rect.top; sx = e.clientX; sy = e.clientY; document.addEventListener('mousemove', move); document.addEventListener('mouseup', up); e.preventDefault(); }); function move(e) { if (!dragging) return; let nx = ox + (e.clientX - sx), ny = oy + (e.clientY - sy); nx = Math.max(0, Math.min(nx, window.innerWidth - root.offsetWidth)); ny = Math.max(0, Math.min(ny, window.innerHeight - 30)); root.style.left = nx + 'px'; root.style.top = ny + 'px'; root.style.right = 'auto'; root.style.bottom = 'auto'; } function up() { dragging = false; document.removeEventListener('mousemove', move); document.removeEventListener('mouseup', up); if (typeof onEnd === 'function') { try { onEnd(); } catch (_) {} } } } function hookConsole() { const orig = { error: console.error, warn: console.warn, info: console.info }; ['error', 'warn', 'info'].forEach(function (lv) { console[lv] = function () { try { const a = Array.prototype.slice.call(arguments).map(function (x) { return (x && x.message) ? x.message : String(x); }).join(' '); if (UI.logEl && a) UI.log('[页:' + lv + '] ' + a.slice(0, 300)); } catch (_) {} orig[lv].apply(console, arguments); }; }); window.addEventListener('error', function (e) { try { UI.log('[页:error] ' + (e.message || (e.error && e.error.message) || e)); } catch (_) {} }); window.addEventListener('unhandledrejection', function (e) { try { UI.log('[页:reject] ' + ((e.reason && e.reason.message) || e.reason)); } catch (_) {} }); } function dumpAllToConsole() { const n = UI.logEl ? UI.logEl.childNodes.length : 0; const txt = UI.logEl ? UI.logEl.innerText : ''; try { console.log('%c[cqrl]════ 运行日志导出 · 共 ' + n + ' 行 ════', 'color:#22d3ee;font-weight:700'); txt.split('\n').forEach(function (line, i) { console.log('[cqrl|' + (i + 1) + '] ' + line); }); console.log('%c[cqrl]════ 导出结束 ════', 'color:#22d3ee;font-weight:700'); } catch (_) {} L('🖥 已把 ' + n + ' 行日志打印到控制台 → 按 F12 → Console,过滤 [cqrl] 即可查看/复制。'); } function collectDiag() { let info = null; try { info = collectCourseCandidates(); } catch (e) {} const lines = [ '=== cqrl 诊断 v' + VER + ' ===', '时间 : ' + new Date().toLocaleString(), 'URL : ' + location.href, '标题 : ' + (document.title || ''), '播放iframe : ' + (info && info.frame && info.frame.raw ? info.frame.raw : '(未找到)'), '学习课 : ' + (info && info.learningId ? info.learningId : '(无)'), '源课(iframe解析) : ' + (info && info.frame && info.frame.sourceId ? info.frame.sourceId : '(无)'), '候选id : ' + (info ? info.cands.join(', ') : '(无)'), '课程名 : ' + (info && info.courseTitle ? info.courseTitle : '(无)'), 'userId : ' + (info && info.userId ? info.userId : '(无)'), '会话 : ' + (info && info.session ? info.session : '(无)'), 'elsSign: ' + (info && info.elsSign ? info.elsSign : '(无)'), '页面完成率 : ' + (info && info.stepRate !== '' ? info.stepRate + '%' : '(无)'), 'provider : ' + (PROVIDER || getFrameProvider() || '(未解析,默认 default)'), '上报接口 : updateCourseRecord + writeRecordWhileClose(双通道)', 'UA : ' + navigator.userAgent, '--- 运行日志 ---', UI.logEl ? UI.logEl.innerText : '', ]; return lines.join('\n'); } async function refreshRate() { if (BUSY) { L('⚠ 正在刷课中,请稍候再查。'); return; } let info = null; try { info = collectCourseCandidates(); } catch (_) {} const lid = info && info.learningId; if (!lid) { L('✗ 未识别到课程 ID,请在「课程学习/播放页」操作。'); return; } UI.setCourse((info && info.courseTitle) || ''); L('🔄 查询完成率…'); try { const kn = await knowStudyRecordEx(lid); const sid = (info.frame && info.frame.sourceId) || lid; let rms = 0; try { rms = await fetchStudyRate(lid, sid); } catch (_) {} const rate = Math.max(Number(kn.rate || 0), Number(rms || 0)); UI.setRealRate(rate); L('· 页面完成率 ' + kn.rate + '% / 接口完成率 ' + rms + '% → ' + rate + '%' + (kn.stepToGetScore ? '(下一步 ' + kn.stepToGetScore + ')' : '') + (kn.rmsNext === '1' ? ' ✔ 已可进入下一步' : '')); } catch (e) { L('✗ 查询失败:' + ((e && e.message) || e)); } } function testChannel() { let info = null; try { info = collectCourseCandidates(); } catch (_) {} const lid = info && info.learningId, uid = (info && info.userId) || getUserId(); if (!lid) { L('✗ 未识别到课程 ID,请在「课程学习/播放页」点击。'); return; } if (!uid) { L('✗ 未取到 userId(本页未内联该变量),官方通道不可用,将只用 HTTP 通道。'); return; } L('📡 测试官方通道:courseId=' + lid.slice(0, 8) + '… userId=' + uid.slice(0, 8) + '…'); postMsgRecordWhileClose(lid, uid, 4000).then(function (r) { L(r.ok ? ' ✓ 通道可用(iframe 已回执)→ 刷课时自动作为第二通道/落库回写' : ' · 通道无回执:' + r.reason + '(不影响主流程,仍走 HTTP 通道)'); }); } async function probeWrite() { let info = null; try { info = collectCourseCandidates(); } catch (_) {} const lid = (info && info.learningId) || ''; if (!lid) { L('🧪 自检失败:本页未识别到课程 ID,请在「课程学习/播放页」点「🧪 自检」。'); return; } const src = (info && info.frame && info.frame.sourceId) || lid; L('🧪 上报自检:courseId=' + lid.slice(0, 8) + '… sourceId=' + src.slice(0, 8) + '…'); const hit = (await tryChapters(src)) || (await tryChapters(lid)); const resources = (hit && hit.resources) || []; L(' · 章节视频 ' + resources.length + ' 个'); for (const pv of providerList()) { L(' ── providerCorpCode=' + pv + ' ──'); let recs = []; try { recs = await fetchStudyRecords(lid, src, pv); } catch (_) {} L(' getStudyRecordList → ' + recs.length + ' 条' + (recs.length ? ',示例 ' + brief(recs[0]) : '')); try { L(' syncStudyRecord → ' + brief(await syncStudyRecord(lid, src, pv))); } catch (e) { L(' syncStudyRecord 异常:' + e); } try { recs = await fetchStudyRecords(lid, src, pv); } catch (_) {} const target = resources[0]; if (!target) { L(' 无视频章节,跳过写入测试'); continue; } const rec = findRecord(recs, target.resourceId); const total = Number((rec && rec.timeToFinish) || target.timeToFinish || 1800); const payload = { recordId: rec ? rec.recordId : null, courseId: lid, sourceId: src, providerCorpCode: pv, chapterId: (rec && rec.chapterId) || target.chapterId || '', resourceId: target.resourceId, timeToFinish: total, currentPosition: total, type: (rec && rec.type) || 'video', currentStudyTime: total, pageIndex: 0, }; let r1 = null, r2 = null; try { r1 = await updateCourseRecord(payload); } catch (e) { r1 = 'EX:' + ((e && e.message) || e); } L(' updateCourseRecord → ' + brief(r1)); try { r2 = await writeRecordWhileClose(payload); } catch (e) { r2 = 'EX:' + ((e && e.message) || e); } L(' writeRecordWhileClose → ' + brief(r2)); await sleep(1200); const after = await fetchStudyRecords(lid, src, pv); const rec2 = findRecord(after, target.resourceId); L(' 写入后:' + (rec2 ? ('position=' + rec2.currentPosition + '/' + rec2.timeToFinish + ' confirmFinish=' + rec2.confirmFinish) : '(仍无该资源记录 → 该 provider 无效)')); } L('🧪 自检结束。请把以上输出发我:看哪个 provider 有记录、写入后 position 是否变化。'); } function injectStyles() { if (document.getElementById('cqrl-style')) return; const css = [ '#cqrl-panel{position:fixed;z-index:99999;background:rgba(17,22,38,.86);backdrop-filter:blur(16px) saturate(140%);-webkit-backdrop-filter:blur(16px) saturate(140%);color:#e6edf7;font:12px/1.5 system-ui,-apple-system,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;border:1px solid rgba(120,140,200,.22);border-radius:16px;box-shadow:0 18px 50px rgba(0,0,0,.55),inset 0 0 0 1px rgba(255,255,255,.03);overflow:hidden;display:flex;flex-direction:column;}', '#cqrl-panel *{box-sizing:border-box;}', '#cqrl-panel .cqrl-head{display:flex;align-items:center;gap:8px;padding:10px 12px;cursor:move;background:linear-gradient(135deg,#6d28d9 0%,#2563eb 55%,#0ea5e9 100%);color:#fff;user-select:none;}', '#cqrl-panel .cqrl-title{font-size:13px;font-weight:800;letter-spacing:.3px;white-space:nowrap;}', '#cqrl-panel .cqrl-ver{font-size:10px;opacity:.85;background:rgba(255,255,255,.18);padding:1px 7px;border-radius:999px;}', '#cqrl-panel .cqrl-head-right{margin-left:auto;display:flex;gap:8px;align-items:center;}', '#cqrl-panel .cqrl-status{font-size:11px;font-weight:700;background:rgba(255,255,255,.16);padding:2px 10px;border-radius:999px;}', '#cqrl-panel .cqrl-ico-btn{cursor:pointer;font-weight:800;font-size:13px;line-height:1;padding:2px 5px;border-radius:7px;transition:.15s;}', '#cqrl-panel .cqrl-ico-btn:hover{background:rgba(255,255,255,.22);}', '#cqrl-panel .cqrl-body{padding:11px 12px;flex:0 0 auto;}', '#cqrl-panel .cqrl-section{font-size:11px;font-weight:700;color:#94a3b8;margin:10px 0 6px;padding-left:4px;letter-spacing:.3px;border-left:3px solid #22d3ee;padding-left:7px;}', '#cqrl-panel .cqrl-section:first-child{margin-top:0;}', '#cqrl-panel .cqrl-row{display:flex;gap:8px;margin-bottom:8px;}', '#cqrl-panel .cqrl-btn{flex:1;border:0;border-radius:10px;padding:9px 8px;font-weight:700;font-size:12px;cursor:pointer;color:#fff;transition:transform .12s,filter .12s;white-space:nowrap;}', '#cqrl-panel .cqrl-btn:hover{transform:translateY(-1px);filter:brightness(1.08);}', '#cqrl-panel .cqrl-btn:active{transform:translateY(0);}', '#cqrl-panel .cqrl-btn-ghost{flex:1;background:rgba(148,163,184,.16);color:#dbe4f0;border:1px solid rgba(148,163,184,.22);}', '#cqrl-panel .cqrl-btn-primary{background:linear-gradient(135deg,#22d3ee,#0ea5e9);color:#06283b;box-shadow:0 6px 16px rgba(14,165,233,.35);}', '#cqrl-panel .cqrl-btn-stop{background:linear-gradient(135deg,#f87171,#ef4444);box-shadow:0 6px 16px rgba(239,68,68,.3);}', '#cqrl-panel .cqrl-btn-batch{background:linear-gradient(135deg,#a78bfa,#7c3aed);box-shadow:0 6px 16px rgba(124,58,237,.32);}', '#cqrl-panel .cqrl-meta{display:flex;align-items:center;gap:6px;font-size:11px;color:#94a3b8;margin:8px 0 7px;}', '#cqrl-panel .cqrl-course{flex:1;color:#cbd5e1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:600;}', '#cqrl-panel .cqrl-stat{display:flex;gap:10px;align-items:center;font-size:11px;color:#94a3b8;margin-bottom:9px;}', '#cqrl-panel .cqrl-pill{background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.08);padding:2px 9px;border-radius:999px;}', '#cqrl-panel .cqrl-pill b{color:#e2e8f0;font-weight:700;}', '#cqrl-panel .cqrl-pill.rate b{color:#67e8f9;}', '#cqrl-panel .cqrl-log{height:160px;overflow:auto;background:rgba(2,6,23,.55);border:1px solid rgba(148,163,184,.14);border-radius:10px;padding:8px;white-space:pre-wrap;word-break:break-all;font-family:Consolas,"Liberation Mono",monospace;font-size:11px;}', '#cqrl-panel .cqrl-log div{padding:1px 0;color:#cbd5e1;}', '#cqrl-panel .cqrl-tools{display:flex;gap:12px;margin-top:9px;font-size:11px;color:#94a3b8;align-items:center;flex-wrap:wrap;}', '#cqrl-panel .cqrl-tools span{cursor:pointer;transition:.15s;}', '#cqrl-panel .cqrl-tools span:hover{color:#e2e8f0;}', '#cqrl-panel .cqrl-accent{color:#67e8f9;}', '#cqrl-panel .cqrl-gold{color:#fbbf24;}', '.cqrl-logo{width:18px;height:18px;border-radius:4px;object-fit:contain;flex:0 0 auto;}', '#cqrl-panel .cqrl-topbar{display:flex;gap:8px;padding:8px 12px;background:rgba(255,255,255,.04);border-bottom:1px solid rgba(255,255,255,.06);}', '#cqrl-panel .cqrl-btn-group{background:linear-gradient(135deg,#34d399,#059669);box-shadow:0 6px 16px rgba(5,150,105,.3);}', '#cqrl-panel .cqrl-btn-gold{background:linear-gradient(135deg,#fbbf24,#f59e0b);color:#3b2406;box-shadow:0 6px 16px rgba(245,158,11,.32);}', '#cqrl-panel .cqrl-btn-gold:hover,#cqrl-panel .cqrl-btn-group:hover{filter:brightness(1.08);}', '#cqrl-panel .cqrl-resize{position:absolute;right:0;bottom:0;width:16px;height:16px;cursor:nwse-resize;background:linear-gradient(135deg,transparent 50%,rgba(103,232,249,.5) 50%);}', '.cqrl-ov{position:fixed;inset:0;z-index:100000;display:none;align-items:center;justify-content:center;background:rgba(2,6,23,.72);backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px);}', '.cqrl-card{width:370px;max-height:86vh;overflow:auto;background:linear-gradient(160deg,#1e293b,#0f172a);border:1px solid rgba(103,232,249,.35);border-radius:18px;padding:22px;color:#e2e8f0;box-shadow:0 24px 70px rgba(0,0,0,.6);font-family:system-ui,-apple-system,"PingFang SC","Microsoft YaHei",sans-serif;}', '.cqrl-card h3{margin:0 0 12px;font-size:17px;display:flex;align-items:center;justify-content:space-between;color:#67e8f9;}', '.cqrl-card h3 .cqrl-close{cursor:pointer;font-size:20px;color:#cbd5e1;line-height:1;}', '.cqrl-card h3 .cqrl-close:hover{color:#fff;}', '.cqrl-card p{font-size:12px;line-height:1.8;color:#cbd5e1;margin:0 0 8px;}', '.cqrl-completion-card{width:320px;text-align:center;padding:26px 22px;background:linear-gradient(160deg,#1e293b,#0f172a);border:1px solid rgba(34,211,238,.45);border-radius:20px;box-shadow:0 24px 70px rgba(0,0,0,.6);}', '.cqrl-completion-icon{font-size:42px;margin-bottom:8px;}', '.cqrl-completion-card h3{margin:0 0 8px;font-size:16px;color:#e2e8f0;}', '.cqrl-completion-title{font-size:15px;font-weight:700;color:#67e8f9;margin-bottom:6px;line-height:1.4;}', '.cqrl-completion-rate{font-size:13px;color:#86efac;font-weight:700;margin-bottom:6px;}', '.cqrl-completion-credit{font-size:12px;color:#fbbf24;margin-bottom:16px;}', '.cqrl-completion-card .cqrl-cta{margin-top:0;background:linear-gradient(135deg,#22d3ee,#0ea5e9);color:#06283b;font-weight:800;}', '.cqrl-card ol{padding-left:18px;margin:0 0 8px;}', '.cqrl-card li{font-size:12px;line-height:1.7;margin-bottom:5px;}', '.cqrl-card b{color:#fbbf24;}', '.cqrl-cta{display:block;width:100%;margin-top:10px;border:0;border-radius:10px;padding:10px;background:linear-gradient(135deg,#22d3ee,#0ea5e9);color:#06283b;font-weight:800;font-size:13px;cursor:pointer;}', '.cqrl-cta:hover{filter:brightness(1.08);}', '.cqrl-reward-card{position:relative;width:400px;padding:0;border-radius:20px;text-align:center;color:#f5f7fa;overflow:hidden;background:linear-gradient(160deg,#0f2027,#203a43,#2c5364);box-shadow:0 0 32px rgba(245,208,97,.22),0 24px 70px rgba(0,0,0,.6);}', '.cqrl-light{position:absolute;inset:0;border-radius:20px;padding:1.5px;background:conic-gradient(from 0deg,#f5d061,#b8860b,#fff3c4,#f5d061,#b8860b,#f5d061);-webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;mask-composite:exclude;animation:cqrl-spin 4s linear infinite;pointer-events:none;}', '.cqrl-sheen{position:absolute;top:0;left:-60%;width:60%;height:100%;background:linear-gradient(100deg,transparent,rgba(255,255,255,.18),transparent);transform:skewX(-18deg);animation:cqrl-sheen 3.6s ease-in-out infinite;pointer-events:none;}', '.cqrl-reward-inner{position:relative;padding:24px;}', '.cqrl-reward-title{font-size:18px;font-weight:800;color:#ffd700;display:flex;align-items:center;justify-content:space-between;}', '.cqrl-reward-title .cqrl-close{cursor:pointer;font-size:20px;color:#cbd5e1;}', '.cqrl-reward-sub{font-size:12px;line-height:1.8;color:#dbeafe;margin:10px 0 16px;}', '.cqrl-qr-row{display:flex;gap:16px;justify-content:center;}', '.cqrl-qr{text-align:center;}', '.cqrl-qr img{display:block;width:160px;height:160px;object-fit:contain;background:#fff;padding:8px;border-radius:12px;border:2px solid rgba(255,255,255,.25);}', '.cqrl-qr-cap{font-size:12px;margin-top:7px;font-weight:700;}', '.cqrl-qr-cap.alipay{color:#7dd3fc;}', '.cqrl-qr-cap.wechat{color:#86efac;}', '.cqrl-reward-foot{font-size:11px;color:#94a3b8;margin-top:16px;letter-spacing:.5px;}', '.cqrl-tut-card{width:360px;}', '.cqrl-steps{margin:6px 0 12px;}', '.cqrl-step{display:flex;gap:10px;align-items:flex-start;margin-bottom:10px;}', '.cqrl-num{flex:0 0 auto;width:22px;height:22px;border-radius:50%;background:linear-gradient(135deg,#22d3ee,#7c3aed);color:#fff;font-weight:800;font-size:12px;display:flex;align-items:center;justify-content:center;}', '.cqrl-step div{font-size:12px;line-height:1.6;color:#cbd5e1;}', '.cqrl-step div b{color:#e2e8f0;}', '@keyframes cqrl-spin{to{transform:rotate(360deg);}}', '@keyframes cqrl-sheen{0%{left:-60%;}60%,100%{left:120%;}}' ].join(''); const st = document.createElement('style'); st.id = 'cqrl-style'; st.textContent = css; (document.head || document.documentElement).appendChild(st); } const REWARD_HTML = '
' + '
' + '
' + '
' + '
💖 感谢支持作者
' + '

这个工具,是我在工作之余一点点打磨出来的。它替你省下的每一分钟,我都希望真的能帮到你。如果它对你有用,欢迎请我喝杯茶 🍵——你的支持,会让我更有动力把它做得更稳、更好。

' + '
' + '
支付宝
支付宝
' + '
微信
微信
' + '
' + '
长按识别 · 自愿打赏 · 每一份心意,我都悄悄记在心里,谢谢你 ❤
' + '
' + '
' + '
'; const HELP_HTML = '
' + '
' + '

📖 使用说明

' + '

本工具能做什么
自动刷完「重庆人社培训网(cqrl.21tb.com)」的课程视频:不真实播放,直连平台接口快速完成进度并同步完成态。

' + '

怎么用(半自动)

' + '
  1. 在「重庆人社培训网」登录后,打开任意一门课程的学习/播放页(地址含 courseStudyItem.learn.do,页面里有视频播放器)。
  2. 点击右侧面板上的「⚡ 后台效率刷」,工具会直连平台接口把本课所有视频进度推满并同步完成态。
  3. 刷课中可随时点「■ 停止」;刷完会弹出「恭喜完成」提示,显示完成率与学分。
  4. 下一门课:再打开它的学习/播放页,重复点「⚡ 后台效率刷」即可。
' + '

工具按钮
· 🧪 自检:真实调用一次进度写入并打印服务器回执,用于定位「完成率始终 0%」。
· 🧾 诊断:复制当前页面的诊断信息(URL/课程id/userId/日志)发我排障。
· 📡 通道:测试平台官方 iframe 上报通道是否可用。
· 🔄 刷新率:向服务器查询当前完成率。

' + '

注意事项
· 课后考试需自行完成。
· 平台若有防刷检测,会自动切换慢速/官方通道重试。
· 遇到问题点「💬 反馈群」加群交流,或点「🧾 诊断」复制信息反馈。

' + '
' + '
'; const TUT_HTML = '
' + '
' + '

👋 欢迎使用 · 两步上手

' + '
' + '
1
打开课程学习/播放页
进入任意一门课程的播放页(地址含 courseStudyItem.learn.do,有视频播放器)。
' + '
2
点「⚡ 后台效率刷」
工具直连平台接口把本课视频进度推满并同步完成态,刷完弹「恭喜完成」。
' + '
' + '

不真实播放视频、不自动翻页;只刷你当前打开的这一门。点「❓ 使用说明」可随时查看完整教程(当前版本 v' + VER + ')。

' + '' + '
' + '
'; const COMPLETION_HTML = '
' + '
' + '
' + '

恭喜您已经完成课程

' + '
' + '
完成率 100%
' + '' + '' + '
' + '
'; function buildOverlays() { if (document.getElementById('cqrl-reward')) return; const wrap = document.createElement('div'); wrap.innerHTML = REWARD_HTML + HELP_HTML + TUT_HTML + COMPLETION_HTML; while (wrap.firstChild) document.body.appendChild(wrap.firstChild); } function extractCredit(title) { if (!title) return ''; const norm = function (s) { return String(s || '').replace(/[\s·•·.,,。、::\-_||“”‘’"'']/g, '').toLowerCase(); }; const nt = norm(title); try { const cards = document.querySelectorAll('.text-item, .course-card, .course-item, .el-card, .card-item'); for (const card of cards) { const nm = card.querySelector('.item__name, .course-title, .course-name, [class*="title"], h3, h4'); const t = (nm && (nm.getAttribute('title') || nm.textContent)) || card.textContent || ''; if (!t) continue; const n2 = norm(t); if (n2 !== nt && n2.indexOf(nt) < 0 && nt.indexOf(n2) < 0) continue; const spans = card.querySelectorAll('.info-num span'); for (const sp of spans) { const txt = (sp.textContent || '').trim(); if (/学分/.test(txt)) return txt; } } } catch (_) {} return ''; } function showCompletionModal(name, rate, credit) { const ov = document.getElementById('cqrl-completion'); if (!ov) return; const titleEl = document.getElementById('cqrl-completion-title'); const rateEl = document.getElementById('cqrl-completion-rate'); const creditEl = document.getElementById('cqrl-completion-credit'); if (titleEl) titleEl.textContent = name || '课程'; if (rateEl) rateEl.textContent = '完成率 ' + (rate || 100) + '%'; if (creditEl) { if (credit) { creditEl.textContent = '您获得 ' + credit + ',' + (credit.replace(/[^0-9.]/g, '') * 1 || 1) + ' 学时!'; creditEl.style.display = 'block'; } else { creditEl.style.display = 'none'; } } ov.style.display = 'flex'; setTimeout(function () { try { ov.style.display = 'none'; } catch (_) {} }, 4500); } function maybeShowTutorial() { try { if (localStorage.getItem('cqrl_tut_v2') === '1') return; } catch (_) {} const t = document.getElementById('cqrl-tut'); if (t) t.style.display = 'flex'; } function dismissTutorial() { try { localStorage.setItem('cqrl_tut_v2', '1'); } catch (_) {} const t = document.getElementById('cqrl-tut'); if (t) t.style.display = 'none'; } function buildPanel() { if (window.__cqrl_injected) return; if (document.getElementById('cqrl-panel')) { window.__cqrl_injected = 1; return; } window.__cqrl_injected = 1; let saved = {}; try { saved = JSON.parse(localStorage.getItem(LS_PANEL) || '{}') || {}; } catch (_) {} injectStyles(); buildOverlays(); const root = document.createElement('div'); root.id = 'cqrl-panel'; root.style.width = (saved.w || 340) + 'px'; if (saved.left) { root.style.left = saved.left; root.style.top = saved.top; root.style.right = 'auto'; root.style.bottom = 'auto'; } else { root.style.right = saved.right || '14px'; root.style.bottom = saved.bottom || '14px'; } root.innerHTML = '
' + '' + '⚡ 重庆专技·自动刷课' + 'v' + VER + '' + '' + '就绪' + '' + '' + '' + '
' + '
' + '' + '' + '
' + '
' + '
⚡ 半自动 · 进入播放页刷本课
' + '
' + '' + '
' + '
🔧 工具
' + '
' + '' + '' + '
' + '
' + '' + '' + '
' + '
' + '' + '
' + '
' + '课程' + '' + '
' + '
' + '进度 0/0' + '完成率 0%' + '
' + '
' + '
' + '❓ 使用说明' + '📋 复制' + '🖥 F12' + '🗑 清空' + '
' + '
' + '
'; document.body.appendChild(root); UI.root = root; UI.logEl = root.querySelector('#cqrl-log'); UI.statusEl = root.querySelector('#cqrl-status'); UI.statsEl = root.querySelector('#cqrl-stats'); UI.rateEl = root.querySelector('#cqrl-rate'); UI.courseEl = root.querySelector('#cqrl-course'); const bodyEl = root.querySelector('#cqrl-body'); const collapseBtn = root.querySelector('#cqrl-collapse'); const persist = function () { try { saved.w = root.offsetWidth; saved.logH = UI.logEl.offsetHeight; saved.left = root.style.left; saved.top = root.style.top; saved.right = root.style.right; saved.bottom = root.style.bottom; saved.collapsed = (bodyEl.style.display === 'none') ? '1' : '0'; localStorage.setItem(LS_PANEL, JSON.stringify(saved)); } catch (_) {} }; const handleEl = root.querySelector('#cqrl-handle'); makeDraggable(root, handleEl, persist); handleEl.addEventListener('dblclick', function () { collapseBtn.click(); }); (function () { const h = root.querySelector('#cqrl-resize'); let on = false, sx = 0, sy = 0, w0 = 0, h0 = 0; h.addEventListener('mousedown', function (e) { on = true; sx = e.clientX; sy = e.clientY; w0 = root.offsetWidth; h0 = UI.logEl.offsetHeight; document.addEventListener('mousemove', mv); document.addEventListener('mouseup', up); e.preventDefault(); e.stopPropagation(); }); function mv(e) { if (!on) return; root.style.width = Math.max(260, Math.min(620, w0 + (e.clientX - sx))) + 'px'; UI.logEl.style.height = Math.max(80, Math.min(560, h0 + (e.clientY - sy))) + 'px'; } function up() { on = false; document.removeEventListener('mousemove', mv); document.removeEventListener('mouseup', up); persist(); } })(); root.querySelector('#cqrl-go').addEventListener('click', startBrush); root.querySelector('#cqrl-stop').addEventListener('click', function () { stopFlag = true; BUSY = false; L('■ 已发送停止(本轮结束后停下)'); UI.setStatus('已停止', '#9ca3af'); }); root.querySelector('#cqrl-copy').addEventListener('click', function () { UI.copyLog(); }); root.querySelector('#cqrl-console').addEventListener('click', dumpAllToConsole); root.querySelector('#cqrl-refresh').addEventListener('click', refreshRate); root.querySelector('#cqrl-chan').addEventListener('click', testChannel); root.querySelector('#cqrl-probe').addEventListener('click', probeWrite); root.querySelector('#cqrl-diag').addEventListener('click', function () { const d = collectDiag(); try { navigator.clipboard.writeText(d); L('🧾 诊断信息已复制(含 URL / 学习课 / 源课 / 候选id / 全部日志),可直接发我排障。'); } catch (_) { L('🧾 剪贴板不可用,已改为打印到控制台。'); dumpAllToConsole(); } }); root.querySelector('#cqrl-clear').addEventListener('click', function () { if (UI.logEl) UI.logEl.innerHTML = ''; }); collapseBtn.addEventListener('click', function () { const collapsed = bodyEl.style.display === 'none'; bodyEl.style.display = collapsed ? 'block' : 'none'; root.querySelector('#cqrl-resize').style.display = collapsed ? 'block' : 'none'; this.textContent = collapsed ? '▾' : '▸'; persist(); }); if (saved.collapsed === '1') collapseBtn.click(); root.querySelector('#cqrl-hide').addEventListener('click', function () { root.style.display = 'none'; let ball = document.getElementById('cqrl-ball'); if (!ball) { ball = document.createElement('div'); ball.id = 'cqrl-ball'; ball.textContent = '⚡'; ball.style.cssText = 'position:fixed;right:14px;bottom:14px;z-index:99999;width:44px;height:44px;border-radius:50%;' + 'background:linear-gradient(135deg,#22d3ee,#0ea5e9);color:#06283b;font-size:20px;display:flex;align-items:center;' + 'justify-content:center;cursor:pointer;box-shadow:0 6px 20px rgba(0,0,0,.45);user-select:none;'; ball.title = '点击展开刷课面板'; ball.addEventListener('click', function () { root.style.display = 'flex'; ball.style.display = 'none'; }); document.body.appendChild(ball); } ball.style.display = 'flex'; }); function openOv(id) { const el = document.getElementById(id); if (el) el.style.display = 'flex'; } function closeOv(id) { const el = document.getElementById(id); if (el) el.style.display = 'none'; } document.getElementById('cqrl-reward-btn').addEventListener('click', function () { openOv('cqrl-reward'); }); document.getElementById('cqrl-group').addEventListener('click', function () { window.open('https://qm.qq.com/q/HmejDdd0Ec', '_blank'); }); document.getElementById('cqrl-help-btn').addEventListener('click', function () { openOv('cqrl-help'); }); document.getElementById('cqrl-reward-close').addEventListener('click', function () { closeOv('cqrl-reward'); }); document.getElementById('cqrl-help-close').addEventListener('click', function () { closeOv('cqrl-help'); }); document.getElementById('cqrl-reward').addEventListener('click', function (e) { if (e.target === this) closeOv('cqrl-reward'); }); document.getElementById('cqrl-help').addEventListener('click', function (e) { if (e.target === this) closeOv('cqrl-help'); }); document.getElementById('cqrl-tut-close').addEventListener('click', dismissTutorial); document.getElementById('cqrl-tut-ok').addEventListener('click', dismissTutorial); document.getElementById('cqrl-tut').addEventListener('click', function (e) { if (e.target === this) dismissTutorial(); }); document.getElementById('cqrl-completion-ok').addEventListener('click', function () { closeOv('cqrl-completion'); }); document.getElementById('cqrl-completion').addEventListener('click', function (e) { if (e.target === this) closeOv('cqrl-completion'); }); maybeShowTutorial(); try { const iv = scanInlineVars(); if (iv.courseTitle) UI.setCourse(iv.courseTitle); if (iv.userId) L('· 已识别 userId:' + iv.userId.slice(0, 8) + '…(官方 iframe 通道可用,点「📡 通道」可测试)'); } catch (_) {} L('💡 就绪:点「⚡ 后台效率刷」= 直连平台接口批量完成进度并同步完成态,无需真实播放视频。'); L('· 使用方法:进入任意一门课程的「学习/播放页」(地址含 courseStudyItem.learn.do、页面有视频播放器)→ 点「⚡ 后台效率刷」即可一键完成本课。'); L('· 双通道:HTTP 直连为主;命中防刷或失败时自动改走官方 iframe 通道(postMessage studyRecordWhileClose)。'); L('· 标题栏可拖动 / 右下角可缩放 / ✕ 最小化 / 📋复制 / 🖥F12 打到控制台 / 🧾诊断 / 🔄刷新率 / 📡通道 / 🧪自检。'); L('· 若「完成率」始终 0%:点「🧪 自检」,它会真实调用一次进度写入并打印服务器回执,把结果发我即可定位。'); } function boot() { if (document.body) buildPanel(); else window.addEventListener('DOMContentLoaded', buildPanel); try { hookConsole(); } catch (_) {} } boot(); })();