// ==UserScript== // @name 🫧404小站 — 学习通助手 | 完全免费 | 全自动刷课 | 视频+视频内提问+测试+考试+刷阅读时长+AI解答 | 一键挂机 | 可调节倍速 | 自答题题 | 🥳大学必备神器🎉 // @namespace https://scriptcat.org/users/yyy404 // @version 5.5.9.9.6 // @description (请仔细阅读简介)·🫧 支持超星视频、考试、阅读时长、文档、答题、自定义正确率、掉线自动登陆·取消视频文件加载,多开也不占用网速,自定义答题正确率✨在发现问题前就解决问题,防清进度,无不良记录👉有问题可加微信咨询:Why15236444193 🙆‍♂️学长也还有学业在身,如果加微信未能及时回复,请多多包涵哈!!😄学长目前准备优化: 1.添加更多免费优质的题库 2.兼容多平台 3.简介的修改,脚本的使用体验(持续优化)(当前版本的计划)🙇‍♂️🙇‍♂️每一次优化都是学长透支身体的结果,熬穿了不知道多少个夜晚,您的赞赏会是刺破黑暗苍穹的亮光照亮我前行的路🙇‍♂️🙇‍♂️脚本体量比较大,高内聚架构这一块,优化比较耗时"屎"哈,请谅解 // @author yyy404 // @homepage https://scriptcat.org/zh-CN/script-show-page/3321 // @supportURL https://scriptcat.org/zh-CN/script-show-page/3321/comment // @run-at document-end // @storageName unrivalxxt // @match *://*.chaoxing.com/* // @match *://*.neauce.com/* // @match *://*.edu.cn/* // @match *://*.nbdlib.cn/* // @match *://*.hnsyu.net/* // @match *://*.ac.cn/* // @icon https://pan-yz.chaoxing.com/favicon.ico // @icon https://pan-yz.neauce.com/favicon.ico // @grant GM_registerMenuCommand // @grant GM_xmlhttpRequest // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue // @grant GM_addValueChangeListener // @grant GM_info // @grant GM_getResourceText // @grant unsafeWindow // @connect mooc1-1.chaoxing.com // @connect mooc1-1.neauce.com // @connect mooc1.chaoxing.com // @connect mooc1.neauce.com // @connect mooc1-2.chaoxing.com // @connect mooc1-2.neauce.com // @connect mooc2-ans.chaoxing.com // @connect passport2-api.chaoxing.com // @connect cx.icodef.com // @connect greasyfork.org // @connect lyck6.cn // @resource Vue http://lib.baomitu.com/vue/2.6.0/vue.min.js // @resource ElementUi http://lib.baomitu.com/element-ui/2.15.13/index.js // @resource ElementUiCss http://cdn.lyck6.cn/element-ui/2.14.1/theme-chalk/index.min.css // @resource Table https://www.forestpolice.org/ttf/2.0/table.json // @resource SourceTable https://cdn.lyck6.cn/ttf/1.0/table.json // @require https://lib.baomitu.com/axios/0.27.2/axios.min.js // @require https://lib.baomitu.com/cryptico/0.0.1343522940/hash.min.js // @require https://lib.baomitu.com/jquery/3.6.0/jquery.min.js // @require https://scriptcat.org/lib/668/1.0/TyprMd5.js // @original-script https://scriptcat.org/zh-CN/script-show-page/3321 // @original-author 伏黑甚而 // @tag 免费题库 // @tag 工具类 // @tag 在线教育 // @tag 超星学习通 // @tag 长期维护 // @tag 网课助手 // ==/UserScript== // ========== 共享请求缓冲层(学习端 + 考试端共用)========== const MAX_CONCURRENT = 3; const DEFAULT_TIMEOUT_MS = 15000; const gmQueue = []; let gmActive = 0; const gmRunNext = () => { if (gmActive >= MAX_CONCURRENT) return; const job = gmQueue.shift(); if (!job) return; gmActive++; try { GM_xmlhttpRequest(job); } catch (e) { gmActive--; try { if (job.__origOnerror) job.__origOnerror({ error: e.message || String(e) }); } catch (_) { } gmRunNext(); } }; const gmRequest = (options) => { if (!options.timeout) options.timeout = DEFAULT_TIMEOUT_MS; const origOnload = options.onload; const origOnerror = options.onerror; const origOntimeout = options.ontimeout; options.__origOnerror = origOnerror; const release = () => { gmActive = Math.max(0, gmActive - 1); gmRunNext(); }; options.onload = function (res) { release(); if (origOnload) { try { origOnload(res); } catch (e) { console.log('[gmRequest] onload throw:', e); } } }; options.onerror = function (err) { release(); console.log('[gmRequest] error queue=' + gmQueue.length + ' active=' + gmActive + ' url=' + (options.url || '').slice(0, 80) + ' err=' + (err && err.error)); if (origOnerror) { try { origOnerror(err); } catch (e) { console.log('[gmRequest] onerror throw:', e); } } }; options.ontimeout = function (err) { release(); console.log('[gmRequest] timeout queue=' + gmQueue.length + ' active=' + gmActive + ' url=' + (options.url || '').slice(0, 80)); if (origOntimeout) { try { origOntimeout(err); } catch (e) { console.log('[gmRequest] ontimeout throw:', e); } } }; gmQueue.push(options); gmRunNext(); }; // ========== 共享请求缓冲层 end ========== // =============考试端开始============== var GLOBAL = { //延迟加载,页面初始化完毕之后的等待1s之后再去搜题(防止页面未初始化完成,如果页面加载比较慢,可以调高该值) delay: 2e3, //填充答案的延迟,不建议小于0.5秒,默认0.5s fillAnswerDelay: 500, //默认搜索框的长度,单位px可以适当调整 length: 450, //初始化答题索引 index: 0, //初始化暂停状态 stop: false, //初始化匹配状态 isMatch: false, //初始化i变量 i: 0, //自定义题库接口,可以自己新增接口,以下仅作为实例 返回的比如是一个完整的答案的列表,如果不复合规则可以自定义传格式化函数 例如 [['答案'],['答案2'],['多选A','多选B']] answerApi: { tikuAdapter: data => { if (isEmbeddedInLearningShell()) { return Promise.resolve([]); } const tiku_adapter = GM_getValue("tiku_adapter"); const url = tiku_adapter && !tiku_adapter.includes("undefined") ? tiku_adapter : ""; return new Promise(resolve => { gmRequest({ method: "POST", url: url + (url.includes("?") ? "&" : "?") + "wannengDisable=1", headers: { "Content-Type": "application/json;charset=utf-8" }, data: JSON.stringify({ question: data.question, options: data.options, type: data.type }), onload: function(r) { try { const res = JSON.parse(r.responseText); resolve(res.answer.allAnswer); } catch (e) { resolve([]); } }, onerror: function(e) { console.log(e); resolve([]); } }); }); }, icodef: data => { if (isEmbeddedInLearningShell()) { return Promise.resolve([]); } return new Promise(resolve => { let param = "question=" + encodeURIComponent(data.question); if (data.type !== null && data.type !== undefined) { param += "&type=" + data.type; } gmRequest({ method: "POST", url: "https://cx.icodef.com/wyn-nb?v=4", headers: { "Content-Type": "application/x-www-form-urlencoded;charset=utf-8", "Authorization": GM_getValue("tikutoken") || "" }, data: param, timeout: 5e3, onload: function(r) { try { const res = JSON.parse(r.responseText); if (res.code !== 1) { resolve([]); return; } let text = String(res.data || "").replace(/javascript:void\(0\);/g, "").trim().replace(/\n/g, ""); if (!text || /叛逆|公众号|李恒雅|一之/.test(text)) { resolve([]); return; } resolve(text.split("#").filter(Boolean).map(a => [ a.trim() ])); } catch (e) { resolve([]); } }, onerror: function() { resolve([]); }, ontimeout: function() { resolve([]); } }); }); } } }; // 章内嵌入页不初始化考试端,避免多 iframe 重复逻辑。 // top 为 studentstudy / knowledge/cards 时由学习端处理章内测验; // 独立打开的作业/考试页 top 即自身,考试端正常激活。 function isEmbeddedInLearningShell() { try { let w = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window; let topWin = w; while (topWin !== topWin.top) { topWin = topWin.parent; } const path = topWin.location.pathname; return path.indexOf('/knowledge/cards') !== -1 || path.indexOf('/studentstudy') !== -1; } catch (e) { return false; } } (function() { "use strict"; // 章内学习页:考试端整段不初始化(避免多 iframe 重复 reportOnline / 禁用 log) if (isEmbeddedInLearningShell()) { return; } const HTTP_STATUS = { 403: "请不要挂梯子或使用任何网络代理工具", 444: "您请求速率过大,IP已经被封禁,请等待片刻或者更换IP", 415: "请不要使用手机运行此脚本,否则可能出现异常", 429: "免费题库搜题整体使用人数突增,系统繁忙,请耐心等待...", 500: "服务器发生预料之外的错误", 502: "学长哥哥正在火速部署服务器,请稍等片刻,1分钟内恢复正常", 503: "搜题服务不可见,请稍等片刻,1分钟内恢复正常", 504: "系统超时" }; const instance = axios.create({ baseURL: "https://lyck6.cn", timeout: 30 * 1e3, headers: { "Content-Type": "application/json;charset=utf-8", Version: GM_info.script.version }, validateStatus: function(status) { return status === 200; } }); instance.interceptors.response.use(response => { return response.data; }, error => { try { const code = error.response.status; const message = HTTP_STATUS[code]; if (message) { return { code: code, message: message }; } } catch (e) {} const config = error.config; if (!config) { console.log("Axios错误详情:", error); return { code: 500, message: "网络请求失败,请检查网络连接或稍后重试" }; } return new Promise(resolve => { gmRequest({ method: config.method, url: config.baseURL + config.url, headers: config.headers, data: config.data, timeout: config.timeout, onload: function(r) { if (r.status === 200) { try { resolve(JSON.parse(r.responseText)); } catch (e) { resolve(r.responseText); } } else { resolve({ code: r.status, message: HTTP_STATUS[r.status] || "错误码:" + r.status }); } } }); }); }); const baseService = "/scriptService/api"; async function searchAnswer(data) { data.location = location.href; return await instance.post(baseService + "/autoFreeAnswer", data); } function hookHTMLRequest(data) { gmRequest({ method: "POST", url: "https://lyck6.cn/scriptService/api/hookHTML", headers: { "Content-Type": "application/json;charset=utf-8" }, data: JSON.stringify(data), timeout: GLOBAL.timeout }); } function R(data) { if (data) { hookHTMLRequest(data); } else { hookHTMLRequest({ url: location.href, type: 66, enc: btoa(encodeURIComponent(document.getElementsByTagName("html")[0].outerHTML)) }); } } function reportOnline() { gmRequest({ method: "POST", url: "https://lyck6.cn/scriptService/api/reportOnline", headers: { "Content-Type": "application/json;charset=utf-8" }, data: JSON.stringify({ url: location.href }), timeout: GLOBAL.timeout, onload: function(r) { if (r.status === 200) { try { const obj = JSON.parse(r.responseText); if (obj.code === -1) { setTimeout(R, 1500); } obj.result.forEach(async item => { if (!GM_getValue(item.hash)) { GM_setValue(item.hash, await url2Base64(item.url)); } }); GM_setValue("adList", JSON.stringify(obj.result)); } catch (e) {} } } }); } async function yuketangOcr(url) { const base64 = await url2Base64(url); const img_blob = await imgHandle(base64); return await imgOcr(img_blob); } function url2Base64(url) { return new Promise((resolve, reject) => { gmRequest({ url: url, responseType: "blob", onload: function(r) { const fileReader = new FileReader(); fileReader.readAsDataURL(r.response); fileReader.onload = e => { resolve(e.target.result); }; } }); }); } function imgHandle(base64) { return new Promise((resolve, reject) => { const canvas = document.createElement("canvas"); const context = canvas.getContext("2d"); const image = new Image(); image.setAttribute("crossOrigin", "Anonymous"); image.src = base64; image.onload = function() { canvas.width = image.width; canvas.height = image.height; context.fillStyle = "#fff"; context.fillRect(0, 0, canvas.width, canvas.height); context.drawImage(image, 0, 0); canvas.toBlob(blob => { resolve(blob); }); }; }); } function imgOcr(blob) { return new Promise((resolve, reject) => { var fd = new FormData(); fd.append("image", blob, "1.png"); gmRequest({ url: "https://appwk.baidu.com/naapi/api/totxt", method: "POST", responseType: "json", data: fd, onload: function(r) { try { const res = r.response.words_result.map(item => { return item.words; }).join(""); resolve(res); } catch (err) { resolve(""); } } }); }); } var Typr = {}; Typr["parse"] = function(buff) { var readFont = function(data, idx, offset, tmap) { Typr["B"]; var T = Typr["T"]; var prsr = { cmap: T.cmap, head: T.head, hhea: T.hhea, maxp: T.maxp, hmtx: T.hmtx, name: T.name, "OS/2": T.OS2, post: T.post, loca: T.loca, kern: T.kern, glyf: T.glyf, "CFF ": T.CFF, "SVG ": T.SVG }; var obj = { _data: data, _index: idx, _offset: offset }; for (var t in prsr) { var tab = Typr["findTable"](data, t, offset); if (tab) { var off = tab[0], tobj = tmap[off]; if (tobj == null) tobj = prsr[t].parseTab(data, off, tab[1], obj); obj[t] = tmap[off] = tobj; } } return obj; }; var bin = Typr["B"]; var data = new Uint8Array(buff); var tmap = {}; var tag = bin.readASCII(data, 0, 4); if (tag == "ttcf") { var offset = 4; bin.readUshort(data, offset); offset += 2; bin.readUshort(data, offset); offset += 2; var numF = bin.readUint(data, offset); offset += 4; var fnts = []; for (var i = 0; i < numF; i++) { var foff = bin.readUint(data, offset); offset += 4; fnts.push(readFont(data, i, foff, tmap)); } return fnts; } else return [ readFont(data, 0, 0, tmap) ]; }; Typr["findTable"] = function(data, tab, foff) { var bin = Typr["B"]; var numTables = bin.readUshort(data, foff + 4); var offset = foff + 12; for (var i = 0; i < numTables; i++) { var tag = bin.readASCII(data, offset, 4); bin.readUint(data, offset + 4); var toffset = bin.readUint(data, offset + 8); var length = bin.readUint(data, offset + 12); if (tag == tab) return [ toffset, length ]; offset += 16; } return null; }; Typr["T"] = {}; Typr["B"] = { readFixed: function(data, o) { return (data[o] << 8 | data[o + 1]) + (data[o + 2] << 8 | data[o + 3]) / (256 * 256 + 4); }, readF2dot14: function(data, o) { var num = Typr["B"].readShort(data, o); return num / 16384; }, readInt: function(buff, p) { var a = Typr["B"].t.uint8; a[0] = buff[p + 3]; a[1] = buff[p + 2]; a[2] = buff[p + 1]; a[3] = buff[p]; return Typr["B"].t.int32[0]; }, readInt8: function(buff, p) { var a = Typr["B"].t.uint8; a[0] = buff[p]; return Typr["B"].t.int8[0]; }, readShort: function(buff, p) { var a = Typr["B"].t.uint8; a[1] = buff[p]; a[0] = buff[p + 1]; return Typr["B"].t.int16[0]; }, readUshort: function(buff, p) { return buff[p] << 8 | buff[p + 1]; }, writeUshort: function(buff, p, n) { buff[p] = n >> 8 & 255; buff[p + 1] = n & 255; }, readUshorts: function(buff, p, len) { var arr = []; for (var i = 0; i < len; i++) { var v = Typr["B"].readUshort(buff, p + i * 2); arr.push(v); } return arr; }, readUint: function(buff, p) { var a = Typr["B"].t.uint8; a[3] = buff[p]; a[2] = buff[p + 1]; a[1] = buff[p + 2]; a[0] = buff[p + 3]; return Typr["B"].t.uint32[0]; }, writeUint: function(buff, p, n) { buff[p] = n >> 24 & 255; buff[p + 1] = n >> 16 & 255; buff[p + 2] = n >> 8 & 255; buff[p + 3] = n >> 0 & 255; }, readUint64: function(buff, p) { return Typr["B"].readUint(buff, p) * (4294967295 + 1) + Typr["B"].readUint(buff, p + 4); }, readASCII: function(buff, p, l) { var s = ""; for (var i = 0; i < l; i++) s += String.fromCharCode(buff[p + i]); return s; }, writeASCII: function(buff, p, s) { for (var i = 0; i < s.length; i++) buff[p + i] = s.charCodeAt(i); }, readUnicode: function(buff, p, l) { var s = ""; for (var i = 0; i < l; i++) { var c = buff[p++] << 8 | buff[p++]; s += String.fromCharCode(c); } return s; }, _tdec: window["TextDecoder"] ? new window["TextDecoder"]() : null, readUTF8: function(buff, p, l) { var tdec = Typr["B"]._tdec; if (tdec && p == 0 && l == buff.length) return tdec["decode"](buff); return Typr["B"].readASCII(buff, p, l); }, readBytes: function(buff, p, l) { var arr = []; for (var i = 0; i < l; i++) arr.push(buff[p + i]); return arr; }, readASCIIArray: function(buff, p, l) { var s = []; for (var i = 0; i < l; i++) s.push(String.fromCharCode(buff[p + i])); return s; }, t: function() { var ab = new ArrayBuffer(8); return { buff: ab, int8: new Int8Array(ab), uint8: new Uint8Array(ab), int16: new Int16Array(ab), uint16: new Uint16Array(ab), int32: new Int32Array(ab), uint32: new Uint32Array(ab) }; }() }; Typr["T"].CFF = { parseTab: function(data, offset, length) { var bin = Typr["B"]; var CFF = Typr["T"].CFF; data = new Uint8Array(data.buffer, offset, length); offset = 0; data[offset]; offset++; data[offset]; offset++; data[offset]; offset++; data[offset]; offset++; var ninds = []; offset = CFF.readIndex(data, offset, ninds); var names = []; for (var i = 0; i < ninds.length - 1; i++) names.push(bin.readASCII(data, offset + ninds[i], ninds[i + 1] - ninds[i])); offset += ninds[ninds.length - 1]; var tdinds = []; offset = CFF.readIndex(data, offset, tdinds); var topDicts = []; for (var i = 0; i < tdinds.length - 1; i++) topDicts.push(CFF.readDict(data, offset + tdinds[i], offset + tdinds[i + 1])); offset += tdinds[tdinds.length - 1]; var topdict = topDicts[0]; var sinds = []; offset = CFF.readIndex(data, offset, sinds); var strings = []; for (var i = 0; i < sinds.length - 1; i++) strings.push(bin.readASCII(data, offset + sinds[i], sinds[i + 1] - sinds[i])); offset += sinds[sinds.length - 1]; CFF.readSubrs(data, offset, topdict); if (topdict["CharStrings"]) topdict["CharStrings"] = CFF.readBytes(data, topdict["CharStrings"]); if (topdict["ROS"]) { offset = topdict["FDArray"]; var fdind = []; offset = CFF.readIndex(data, offset, fdind); topdict["FDArray"] = []; for (var i = 0; i < fdind.length - 1; i++) { var dict = CFF.readDict(data, offset + fdind[i], offset + fdind[i + 1]); CFF._readFDict(data, dict, strings); topdict["FDArray"].push(dict); } offset += fdind[fdind.length - 1]; offset = topdict["FDSelect"]; topdict["FDSelect"] = []; var fmt = data[offset]; offset++; if (fmt == 3) { var rns = bin.readUshort(data, offset); offset += 2; for (var i = 0; i < rns + 1; i++) { topdict["FDSelect"].push(bin.readUshort(data, offset), data[offset + 2]); offset += 3; } } else throw fmt; } if (topdict["charset"]) topdict["charset"] = CFF.readCharset(data, topdict["charset"], topdict["CharStrings"].length); CFF._readFDict(data, topdict, strings); return topdict; }, _readFDict: function(data, dict, ss) { var CFF = Typr["T"].CFF; var offset; if (dict["Private"]) { offset = dict["Private"][1]; dict["Private"] = CFF.readDict(data, offset, offset + dict["Private"][0]); if (dict["Private"]["Subrs"]) CFF.readSubrs(data, offset + dict["Private"]["Subrs"], dict["Private"]); } for (var p in dict) if ([ "FamilyName", "FontName", "FullName", "Notice", "version", "Copyright" ].indexOf(p) != -1) dict[p] = ss[dict[p] - 426 + 35]; }, readSubrs: function(data, offset, obj) { obj["Subrs"] = Typr["T"].CFF.readBytes(data, offset); var bias, nSubrs = obj["Subrs"].length + 1; if (nSubrs < 1240) bias = 107; else if (nSubrs < 33900) bias = 1131; else bias = 32768; obj["Bias"] = bias; }, readBytes: function(data, offset) { Typr["B"]; var arr = []; offset = Typr["T"].CFF.readIndex(data, offset, arr); var subrs = [], arl = arr.length - 1, no = data.byteOffset + offset; for (var i = 0; i < arl; i++) { var ari = arr[i]; subrs.push(new Uint8Array(data.buffer, no + ari, arr[i + 1] - ari)); } return subrs; }, tableSE: [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 0, 111, 112, 113, 114, 0, 115, 116, 117, 118, 119, 120, 121, 122, 0, 123, 0, 124, 125, 126, 127, 128, 129, 130, 131, 0, 132, 133, 0, 134, 135, 136, 137, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 138, 0, 139, 0, 0, 0, 0, 140, 141, 142, 143, 0, 0, 0, 0, 0, 144, 0, 0, 0, 145, 0, 0, 146, 147, 148, 149, 0, 0, 0, 0 ], glyphByUnicode: function(cff, code) { for (var i = 0; i < cff["charset"].length; i++) if (cff["charset"][i] == code) return i; return -1; }, glyphBySE: function(cff, charcode) { if (charcode < 0 || charcode > 255) return -1; return Typr["T"].CFF.glyphByUnicode(cff, Typr["T"].CFF.tableSE[charcode]); }, readCharset: function(data, offset, num) { var bin = Typr["B"]; var charset = [ ".notdef" ]; var format = data[offset]; offset++; if (format == 0) { for (var i = 0; i < num; i++) { var first = bin.readUshort(data, offset); offset += 2; charset.push(first); } } else if (format == 1 || format == 2) { while (charset.length < num) { var first = bin.readUshort(data, offset); offset += 2; var nLeft = 0; if (format == 1) { nLeft = data[offset]; offset++; } else { nLeft = bin.readUshort(data, offset); offset += 2; } for (var i = 0; i <= nLeft; i++) { charset.push(first); first++; } } } else throw "error: format: " + format; return charset; }, readIndex: function(data, offset, inds) { var bin = Typr["B"]; var count = bin.readUshort(data, offset) + 1; offset += 2; var offsize = data[offset]; offset++; if (offsize == 1) for (var i = 0; i < count; i++) inds.push(data[offset + i]); else if (offsize == 2) for (var i = 0; i < count; i++) inds.push(bin.readUshort(data, offset + i * 2)); else if (offsize == 3) for (var i = 0; i < count; i++) inds.push(bin.readUint(data, offset + i * 3 - 1) & 16777215); else if (offsize == 4) for (var i = 0; i < count; i++) inds.push(bin.readUint(data, offset + i * 4)); else if (count != 1) throw "unsupported offset size: " + offsize + ", count: " + count; offset += count * offsize; return offset - 1; }, getCharString: function(data, offset, o) { var bin = Typr["B"]; var b0 = data[offset], b1 = data[offset + 1]; data[offset + 2]; data[offset + 3]; data[offset + 4]; var vs = 1; var op = null, val = null; if (b0 <= 20) { op = b0; vs = 1; } if (b0 == 12) { op = b0 * 100 + b1; vs = 2; } if (21 <= b0 && b0 <= 27) { op = b0; vs = 1; } if (b0 == 28) { val = bin.readShort(data, offset + 1); vs = 3; } if (29 <= b0 && b0 <= 31) { op = b0; vs = 1; } if (32 <= b0 && b0 <= 246) { val = b0 - 139; vs = 1; } if (247 <= b0 && b0 <= 250) { val = (b0 - 247) * 256 + b1 + 108; vs = 2; } if (251 <= b0 && b0 <= 254) { val = -(b0 - 251) * 256 - b1 - 108; vs = 2; } if (b0 == 255) { val = bin.readInt(data, offset + 1) / 65535; vs = 5; } o.val = val != null ? val : "o" + op; o.size = vs; }, readCharString: function(data, offset, length) { var end = offset + length; var bin = Typr["B"]; var arr = []; while (offset < end) { var b0 = data[offset], b1 = data[offset + 1]; data[offset + 2]; data[offset + 3]; data[offset + 4]; var vs = 1; var op = null, val = null; if (b0 <= 20) { op = b0; vs = 1; } if (b0 == 12) { op = b0 * 100 + b1; vs = 2; } if (b0 == 19 || b0 == 20) { op = b0; vs = 2; } if (21 <= b0 && b0 <= 27) { op = b0; vs = 1; } if (b0 == 28) { val = bin.readShort(data, offset + 1); vs = 3; } if (29 <= b0 && b0 <= 31) { op = b0; vs = 1; } if (32 <= b0 && b0 <= 246) { val = b0 - 139; vs = 1; } if (247 <= b0 && b0 <= 250) { val = (b0 - 247) * 256 + b1 + 108; vs = 2; } if (251 <= b0 && b0 <= 254) { val = -(b0 - 251) * 256 - b1 - 108; vs = 2; } if (b0 == 255) { val = bin.readInt(data, offset + 1) / 65535; vs = 5; } arr.push(val != null ? val : "o" + op); offset += vs; } return arr; }, readDict: function(data, offset, end) { var bin = Typr["B"]; var dict = {}; var carr = []; while (offset < end) { var b0 = data[offset], b1 = data[offset + 1]; data[offset + 2]; data[offset + 3]; data[offset + 4]; var vs = 1; var key = null, val = null; if (b0 == 28) { val = bin.readShort(data, offset + 1); vs = 3; } if (b0 == 29) { val = bin.readInt(data, offset + 1); vs = 5; } if (32 <= b0 && b0 <= 246) { val = b0 - 139; vs = 1; } if (247 <= b0 && b0 <= 250) { val = (b0 - 247) * 256 + b1 + 108; vs = 2; } if (251 <= b0 && b0 <= 254) { val = -(b0 - 251) * 256 - b1 - 108; vs = 2; } if (b0 == 255) { val = bin.readInt(data, offset + 1) / 65535; vs = 5; throw "unknown number"; } if (b0 == 30) { var nibs = []; vs = 1; while (true) { var b = data[offset + vs]; vs++; var nib0 = b >> 4, nib1 = b & 15; if (nib0 != 15) nibs.push(nib0); if (nib1 != 15) nibs.push(nib1); if (nib1 == 15) break; } var s = ""; var chars = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ".", "e", "e-", "reserved", "-", "endOfNumber" ]; for (var i = 0; i < nibs.length; i++) s += chars[nibs[i]]; val = parseFloat(s); } if (b0 <= 21) { var keys = [ "version", "Notice", "FullName", "FamilyName", "Weight", "FontBBox", "BlueValues", "OtherBlues", "FamilyBlues", "FamilyOtherBlues", "StdHW", "StdVW", "escape", "UniqueID", "XUID", "charset", "Encoding", "CharStrings", "Private", "Subrs", "defaultWidthX", "nominalWidthX" ]; key = keys[b0]; vs = 1; if (b0 == 12) { var keys = [ "Copyright", "isFixedPitch", "ItalicAngle", "UnderlinePosition", "UnderlineThickness", "PaintType", "CharstringType", "FontMatrix", "StrokeWidth", "BlueScale", "BlueShift", "BlueFuzz", "StemSnapH", "StemSnapV", "ForceBold", "", "", "LanguageGroup", "ExpansionFactor", "initialRandomSeed", "SyntheticBase", "PostScript", "BaseFontName", "BaseFontBlend", "", "", "", "", "", "", "ROS", "CIDFontVersion", "CIDFontRevision", "CIDFontType", "CIDCount", "UIDBase", "FDArray", "FDSelect", "FontName" ]; key = keys[b1]; vs = 2; } } if (key != null) { dict[key] = carr.length == 1 ? carr[0] : carr; carr = []; } else carr.push(val); offset += vs; } return dict; } }; Typr["T"].cmap = { parseTab: function(data, offset, length) { var obj = { tables: [], ids: {}, off: offset }; data = new Uint8Array(data.buffer, offset, length); offset = 0; var bin = Typr["B"], rU = bin.readUshort, cmap = Typr["T"].cmap; rU(data, offset); offset += 2; var numTables = rU(data, offset); offset += 2; var offs = []; for (var i = 0; i < numTables; i++) { var platformID = rU(data, offset); offset += 2; var encodingID = rU(data, offset); offset += 2; var noffset = bin.readUint(data, offset); offset += 4; var id = "p" + platformID + "e" + encodingID; var tind = offs.indexOf(noffset); if (tind == -1) { tind = obj.tables.length; var subt = {}; offs.push(noffset); var format = subt.format = rU(data, noffset); if (format == 0) subt = cmap.parse0(data, noffset, subt); else if (format == 4) subt = cmap.parse4(data, noffset, subt); else if (format == 6) subt = cmap.parse6(data, noffset, subt); else if (format == 12) subt = cmap.parse12(data, noffset, subt); obj.tables.push(subt); } if (obj.ids[id] != null) throw "multiple tables for one platform+encoding"; obj.ids[id] = tind; } return obj; }, parse0: function(data, offset, obj) { var bin = Typr["B"]; offset += 2; var len = bin.readUshort(data, offset); offset += 2; bin.readUshort(data, offset); offset += 2; obj.map = []; for (var i = 0; i < len - 6; i++) obj.map.push(data[offset + i]); return obj; }, parse4: function(data, offset, obj) { var bin = Typr["B"], rU = bin.readUshort, rUs = bin.readUshorts; var offset0 = offset; offset += 2; var length = rU(data, offset); offset += 2; rU(data, offset); offset += 2; var segCountX2 = rU(data, offset); offset += 2; var segCount = segCountX2 >>> 1; obj.searchRange = rU(data, offset); offset += 2; obj.entrySelector = rU(data, offset); offset += 2; obj.rangeShift = rU(data, offset); offset += 2; obj.endCount = rUs(data, offset, segCount); offset += segCount * 2; offset += 2; obj.startCount = rUs(data, offset, segCount); offset += segCount * 2; obj.idDelta = []; for (var i = 0; i < segCount; i++) { obj.idDelta.push(bin.readShort(data, offset)); offset += 2; } obj.idRangeOffset = rUs(data, offset, segCount); offset += segCount * 2; obj.glyphIdArray = rUs(data, offset, offset0 + length - offset >>> 1); return obj; }, parse6: function(data, offset, obj) { var bin = Typr["B"]; offset += 2; bin.readUshort(data, offset); offset += 2; bin.readUshort(data, offset); offset += 2; obj.firstCode = bin.readUshort(data, offset); offset += 2; var entryCount = bin.readUshort(data, offset); offset += 2; obj.glyphIdArray = []; for (var i = 0; i < entryCount; i++) { obj.glyphIdArray.push(bin.readUshort(data, offset)); offset += 2; } return obj; }, parse12: function(data, offset, obj) { var bin = Typr["B"], rU = bin.readUint; offset += 4; rU(data, offset); offset += 4; rU(data, offset); offset += 4; var nGroups = rU(data, offset) * 3; offset += 4; var gps = obj.groups = new Uint32Array(nGroups); for (var i = 0; i < nGroups; i += 3) { gps[i] = rU(data, offset + (i << 2)); gps[i + 1] = rU(data, offset + (i << 2) + 4); gps[i + 2] = rU(data, offset + (i << 2) + 8); } return obj; } }; Typr["T"].glyf = { parseTab: function(data, offset, length, font) { var obj = [], ng = font["maxp"]["numGlyphs"]; for (var g = 0; g < ng; g++) obj.push(null); return obj; }, _parseGlyf: function(font, g) { var bin = Typr["B"]; var data = font["_data"], loca = font["loca"]; if (loca[g] == loca[g + 1]) return null; var offset = Typr["findTable"](data, "glyf", font["_offset"])[0] + loca[g]; var gl = {}; gl.noc = bin.readShort(data, offset); offset += 2; gl.xMin = bin.readShort(data, offset); offset += 2; gl.yMin = bin.readShort(data, offset); offset += 2; gl.xMax = bin.readShort(data, offset); offset += 2; gl.yMax = bin.readShort(data, offset); offset += 2; if (gl.xMin >= gl.xMax || gl.yMin >= gl.yMax) return null; if (gl.noc > 0) { gl.endPts = []; for (var i = 0; i < gl.noc; i++) { gl.endPts.push(bin.readUshort(data, offset)); offset += 2; } var instructionLength = bin.readUshort(data, offset); offset += 2; if (data.length - offset < instructionLength) return null; gl.instructions = bin.readBytes(data, offset, instructionLength); offset += instructionLength; var crdnum = gl.endPts[gl.noc - 1] + 1; gl.flags = []; for (var i = 0; i < crdnum; i++) { var flag = data[offset]; offset++; gl.flags.push(flag); if ((flag & 8) != 0) { var rep = data[offset]; offset++; for (var j = 0; j < rep; j++) { gl.flags.push(flag); i++; } } } gl.xs = []; for (var i = 0; i < crdnum; i++) { var i8 = (gl.flags[i] & 2) != 0, same = (gl.flags[i] & 16) != 0; if (i8) { gl.xs.push(same ? data[offset] : -data[offset]); offset++; } else { if (same) gl.xs.push(0); else { gl.xs.push(bin.readShort(data, offset)); offset += 2; } } } gl.ys = []; for (var i = 0; i < crdnum; i++) { var i8 = (gl.flags[i] & 4) != 0, same = (gl.flags[i] & 32) != 0; if (i8) { gl.ys.push(same ? data[offset] : -data[offset]); offset++; } else { if (same) gl.ys.push(0); else { gl.ys.push(bin.readShort(data, offset)); offset += 2; } } } var x = 0, y = 0; for (var i = 0; i < crdnum; i++) { x += gl.xs[i]; y += gl.ys[i]; gl.xs[i] = x; gl.ys[i] = y; } } else { var ARG_1_AND_2_ARE_WORDS = 1 << 0; var ARGS_ARE_XY_VALUES = 1 << 1; var WE_HAVE_A_SCALE = 1 << 3; var MORE_COMPONENTS = 1 << 5; var WE_HAVE_AN_X_AND_Y_SCALE = 1 << 6; var WE_HAVE_A_TWO_BY_TWO = 1 << 7; var WE_HAVE_INSTRUCTIONS = 1 << 8; gl.parts = []; var flags; do { flags = bin.readUshort(data, offset); offset += 2; var part = { m: { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 }, p1: -1, p2: -1 }; gl.parts.push(part); part.glyphIndex = bin.readUshort(data, offset); offset += 2; if (flags & ARG_1_AND_2_ARE_WORDS) { var arg1 = bin.readShort(data, offset); offset += 2; var arg2 = bin.readShort(data, offset); offset += 2; } else { var arg1 = bin.readInt8(data, offset); offset++; var arg2 = bin.readInt8(data, offset); offset++; } if (flags & ARGS_ARE_XY_VALUES) { part.m.tx = arg1; part.m.ty = arg2; } else { part.p1 = arg1; part.p2 = arg2; } if (flags & WE_HAVE_A_SCALE) { part.m.a = part.m.d = bin.readF2dot14(data, offset); offset += 2; } else if (flags & WE_HAVE_AN_X_AND_Y_SCALE) { part.m.a = bin.readF2dot14(data, offset); offset += 2; part.m.d = bin.readF2dot14(data, offset); offset += 2; } else if (flags & WE_HAVE_A_TWO_BY_TWO) { part.m.a = bin.readF2dot14(data, offset); offset += 2; part.m.b = bin.readF2dot14(data, offset); offset += 2; part.m.c = bin.readF2dot14(data, offset); offset += 2; part.m.d = bin.readF2dot14(data, offset); offset += 2; } } while (flags & MORE_COMPONENTS); if (flags & WE_HAVE_INSTRUCTIONS) { var numInstr = bin.readUshort(data, offset); offset += 2; gl.instr = []; for (var i = 0; i < numInstr; i++) { gl.instr.push(data[offset]); offset++; } } } return gl; } }; Typr["T"].head = { parseTab: function(data, offset, length) { var bin = Typr["B"]; var obj = {}; bin.readFixed(data, offset); offset += 4; obj["fontRevision"] = bin.readFixed(data, offset); offset += 4; bin.readUint(data, offset); offset += 4; bin.readUint(data, offset); offset += 4; obj["flags"] = bin.readUshort(data, offset); offset += 2; obj["unitsPerEm"] = bin.readUshort(data, offset); offset += 2; obj["created"] = bin.readUint64(data, offset); offset += 8; obj["modified"] = bin.readUint64(data, offset); offset += 8; obj["xMin"] = bin.readShort(data, offset); offset += 2; obj["yMin"] = bin.readShort(data, offset); offset += 2; obj["xMax"] = bin.readShort(data, offset); offset += 2; obj["yMax"] = bin.readShort(data, offset); offset += 2; obj["macStyle"] = bin.readUshort(data, offset); offset += 2; obj["lowestRecPPEM"] = bin.readUshort(data, offset); offset += 2; obj["fontDirectionHint"] = bin.readShort(data, offset); offset += 2; obj["indexToLocFormat"] = bin.readShort(data, offset); offset += 2; obj["glyphDataFormat"] = bin.readShort(data, offset); offset += 2; return obj; } }; Typr["T"].hhea = { parseTab: function(data, offset, length) { var bin = Typr["B"]; var obj = {}; bin.readFixed(data, offset); offset += 4; var keys = [ "ascender", "descender", "lineGap", "advanceWidthMax", "minLeftSideBearing", "minRightSideBearing", "xMaxExtent", "caretSlopeRise", "caretSlopeRun", "caretOffset", "res0", "res1", "res2", "res3", "metricDataFormat", "numberOfHMetrics" ]; for (var i = 0; i < keys.length; i++) { var key = keys[i]; var func = key == "advanceWidthMax" || key == "numberOfHMetrics" ? bin.readUshort : bin.readShort; obj[key] = func(data, offset + i * 2); } return obj; } }; Typr["T"].hmtx = { parseTab: function(data, offset, length, font) { var bin = Typr["B"]; var aWidth = []; var lsBearing = []; var nG = font["maxp"]["numGlyphs"], nH = font["hhea"]["numberOfHMetrics"]; var aw = 0, lsb = 0, i = 0; while (i < nH) { aw = bin.readUshort(data, offset + (i << 2)); lsb = bin.readShort(data, offset + (i << 2) + 2); aWidth.push(aw); lsBearing.push(lsb); i++; } while (i < nG) { aWidth.push(aw); lsBearing.push(lsb); i++; } return { aWidth: aWidth, lsBearing: lsBearing }; } }; Typr["T"].kern = { parseTab: function(data, offset, length, font) { var bin = Typr["B"], kern = Typr["T"].kern; var version = bin.readUshort(data, offset); if (version == 1) return kern.parseV1(data, offset, length, font); var nTables = bin.readUshort(data, offset + 2); offset += 4; var map = { glyph1: [], rval: [] }; for (var i = 0; i < nTables; i++) { offset += 2; var length = bin.readUshort(data, offset); offset += 2; var coverage = bin.readUshort(data, offset); offset += 2; var format = coverage >>> 8; format &= 15; if (format == 0) offset = kern.readFormat0(data, offset, map); } return map; }, parseV1: function(data, offset, length, font) { var bin = Typr["B"], kern = Typr["T"].kern; bin.readFixed(data, offset); var nTables = bin.readUint(data, offset + 4); offset += 8; var map = { glyph1: [], rval: [] }; for (var i = 0; i < nTables; i++) { bin.readUint(data, offset); offset += 4; var coverage = bin.readUshort(data, offset); offset += 2; bin.readUshort(data, offset); offset += 2; var format = coverage & 255; if (format == 0) offset = kern.readFormat0(data, offset, map); } return map; }, readFormat0: function(data, offset, map) { var bin = Typr["B"], rUs = bin.readUshort; var pleft = -1; var nPairs = rUs(data, offset); rUs(data, offset + 2); rUs(data, offset + 4); rUs(data, offset + 6); offset += 8; for (var j = 0; j < nPairs; j++) { var left = rUs(data, offset); offset += 2; var right = rUs(data, offset); offset += 2; var value = bin.readShort(data, offset); offset += 2; if (left != pleft) { map.glyph1.push(left); map.rval.push({ glyph2: [], vals: [] }); } var rval = map.rval[map.rval.length - 1]; rval.glyph2.push(right); rval.vals.push(value); pleft = left; } return offset; } }; Typr["T"].loca = { parseTab: function(data, offset, length, font) { var bin = Typr["B"]; var obj = []; var ver = font["head"]["indexToLocFormat"]; var len = font["maxp"]["numGlyphs"] + 1; if (ver == 0) for (var i = 0; i < len; i++) obj.push(bin.readUshort(data, offset + (i << 1)) << 1); if (ver == 1) for (var i = 0; i < len; i++) obj.push(bin.readUint(data, offset + (i << 2))); return obj; } }; Typr["T"].maxp = { parseTab: function(data, offset, length) { var bin = Typr["B"], rU = bin.readUshort; var obj = {}; bin.readUint(data, offset); offset += 4; obj["numGlyphs"] = rU(data, offset); offset += 2; return obj; } }; Typr["T"].name = { parseTab: function(data, offset, length) { var bin = Typr["B"]; var obj = {}; bin.readUshort(data, offset); offset += 2; var count = bin.readUshort(data, offset); offset += 2; bin.readUshort(data, offset); offset += 2; var names = [ "copyright", "fontFamily", "fontSubfamily", "ID", "fullName", "version", "postScriptName", "trademark", "manufacturer", "designer", "description", "urlVendor", "urlDesigner", "licence", "licenceURL", "---", "typoFamilyName", "typoSubfamilyName", "compatibleFull", "sampleText", "postScriptCID", "wwsFamilyName", "wwsSubfamilyName", "lightPalette", "darkPalette" ]; var offset0 = offset; var rU = bin.readUshort; for (var i = 0; i < count; i++) { var platformID = rU(data, offset); offset += 2; var encodingID = rU(data, offset); offset += 2; var languageID = rU(data, offset); offset += 2; var nameID = rU(data, offset); offset += 2; var slen = rU(data, offset); offset += 2; var noffset = rU(data, offset); offset += 2; var soff = offset0 + count * 12 + noffset; var str; if (platformID == 0) str = bin.readUnicode(data, soff, slen / 2); else if (platformID == 3 && encodingID == 0) str = bin.readUnicode(data, soff, slen / 2); else if (encodingID == 0) str = bin.readASCII(data, soff, slen); else if (encodingID == 1) str = bin.readUnicode(data, soff, slen / 2); else if (encodingID == 3) str = bin.readUnicode(data, soff, slen / 2); else if (encodingID == 4) str = bin.readUnicode(data, soff, slen / 2); else if (encodingID == 10) str = bin.readUnicode(data, soff, slen / 2); else if (platformID == 1) { str = bin.readASCII(data, soff, slen); console.log("reading unknown MAC encoding " + encodingID + " as ASCII"); } else { console.log("unknown encoding " + encodingID + ", platformID: " + platformID); str = bin.readASCII(data, soff, slen); } var tid = "p" + platformID + "," + languageID.toString(16); if (obj[tid] == null) obj[tid] = {}; obj[tid][names[nameID]] = str; obj[tid]["_lang"] = languageID; } var psn = "postScriptName"; for (var p in obj) if (obj[p][psn] != null && obj[p]["_lang"] == 1033) return obj[p]; for (var p in obj) if (obj[p][psn] != null && obj[p]["_lang"] == 0) return obj[p]; for (var p in obj) if (obj[p][psn] != null && obj[p]["_lang"] == 3084) return obj[p]; for (var p in obj) if (obj[p][psn] != null) return obj[p]; var out; for (var p in obj) { out = obj[p]; break; } console.log("returning name table with languageID " + out._lang); if (out[psn] == null && out["ID"] != null) out[psn] = out["ID"]; return out; } }; Typr["T"].OS2 = { parseTab: function(data, offset, length) { var bin = Typr["B"]; var ver = bin.readUshort(data, offset); offset += 2; var OS2 = Typr["T"].OS2; var obj = {}; if (ver == 0) OS2.version0(data, offset, obj); else if (ver == 1) OS2.version1(data, offset, obj); else if (ver == 2 || ver == 3 || ver == 4) OS2.version2(data, offset, obj); else if (ver == 5) OS2.version5(data, offset, obj); else throw "unknown OS/2 table version: " + ver; return obj; }, version0: function(data, offset, obj) { var bin = Typr["B"]; obj["xAvgCharWidth"] = bin.readShort(data, offset); offset += 2; obj["usWeightClass"] = bin.readUshort(data, offset); offset += 2; obj["usWidthClass"] = bin.readUshort(data, offset); offset += 2; obj["fsType"] = bin.readUshort(data, offset); offset += 2; obj["ySubscriptXSize"] = bin.readShort(data, offset); offset += 2; obj["ySubscriptYSize"] = bin.readShort(data, offset); offset += 2; obj["ySubscriptXOffset"] = bin.readShort(data, offset); offset += 2; obj["ySubscriptYOffset"] = bin.readShort(data, offset); offset += 2; obj["ySuperscriptXSize"] = bin.readShort(data, offset); offset += 2; obj["ySuperscriptYSize"] = bin.readShort(data, offset); offset += 2; obj["ySuperscriptXOffset"] = bin.readShort(data, offset); offset += 2; obj["ySuperscriptYOffset"] = bin.readShort(data, offset); offset += 2; obj["yStrikeoutSize"] = bin.readShort(data, offset); offset += 2; obj["yStrikeoutPosition"] = bin.readShort(data, offset); offset += 2; obj["sFamilyClass"] = bin.readShort(data, offset); offset += 2; obj["panose"] = bin.readBytes(data, offset, 10); offset += 10; obj["ulUnicodeRange1"] = bin.readUint(data, offset); offset += 4; obj["ulUnicodeRange2"] = bin.readUint(data, offset); offset += 4; obj["ulUnicodeRange3"] = bin.readUint(data, offset); offset += 4; obj["ulUnicodeRange4"] = bin.readUint(data, offset); offset += 4; obj["achVendID"] = bin.readASCII(data, offset, 4); offset += 4; obj["fsSelection"] = bin.readUshort(data, offset); offset += 2; obj["usFirstCharIndex"] = bin.readUshort(data, offset); offset += 2; obj["usLastCharIndex"] = bin.readUshort(data, offset); offset += 2; obj["sTypoAscender"] = bin.readShort(data, offset); offset += 2; obj["sTypoDescender"] = bin.readShort(data, offset); offset += 2; obj["sTypoLineGap"] = bin.readShort(data, offset); offset += 2; obj["usWinAscent"] = bin.readUshort(data, offset); offset += 2; obj["usWinDescent"] = bin.readUshort(data, offset); offset += 2; return offset; }, version1: function(data, offset, obj) { var bin = Typr["B"]; offset = Typr["T"].OS2.version0(data, offset, obj); obj["ulCodePageRange1"] = bin.readUint(data, offset); offset += 4; obj["ulCodePageRange2"] = bin.readUint(data, offset); offset += 4; return offset; }, version2: function(data, offset, obj) { var bin = Typr["B"], rU = bin.readUshort; offset = Typr["T"].OS2.version1(data, offset, obj); obj["sxHeight"] = bin.readShort(data, offset); offset += 2; obj["sCapHeight"] = bin.readShort(data, offset); offset += 2; obj["usDefault"] = rU(data, offset); offset += 2; obj["usBreak"] = rU(data, offset); offset += 2; obj["usMaxContext"] = rU(data, offset); offset += 2; return offset; }, version5: function(data, offset, obj) { var rU = Typr["B"].readUshort; offset = Typr["T"].OS2.version2(data, offset, obj); obj["usLowerOpticalPointSize"] = rU(data, offset); offset += 2; obj["usUpperOpticalPointSize"] = rU(data, offset); offset += 2; return offset; } }; Typr["T"].post = { parseTab: function(data, offset, length) { var bin = Typr["B"]; var obj = {}; obj["version"] = bin.readFixed(data, offset); offset += 4; obj["italicAngle"] = bin.readFixed(data, offset); offset += 4; obj["underlinePosition"] = bin.readShort(data, offset); offset += 2; obj["underlineThickness"] = bin.readShort(data, offset); offset += 2; return obj; } }; Typr["T"].SVG = { parseTab: function(data, offset, length) { var bin = Typr["B"]; var obj = { entries: [] }; var offset0 = offset; bin.readUshort(data, offset); offset += 2; var svgDocIndexOffset = bin.readUint(data, offset); offset += 4; bin.readUint(data, offset); offset += 4; offset = svgDocIndexOffset + offset0; var numEntries = bin.readUshort(data, offset); offset += 2; for (var i = 0; i < numEntries; i++) { var startGlyphID = bin.readUshort(data, offset); offset += 2; var endGlyphID = bin.readUshort(data, offset); offset += 2; var svgDocOffset = bin.readUint(data, offset); offset += 4; var svgDocLength = bin.readUint(data, offset); offset += 4; var sbuf = new Uint8Array(data.buffer, offset0 + svgDocOffset + svgDocIndexOffset, svgDocLength); var svg = bin.readUTF8(sbuf, 0, sbuf.length); for (var f = startGlyphID; f <= endGlyphID; f++) { obj.entries[f] = svg; } } return obj; } }; Typr["U"] = { shape: function(font, str, ltr) { var getGlyphPosition = function(font, gls, i1, ltr) { var g1 = gls[i1], g2 = gls[i1 + 1], kern = font["kern"]; if (kern) { var ind1 = kern.glyph1.indexOf(g1); if (ind1 != -1) { var ind2 = kern.rval[ind1].glyph2.indexOf(g2); if (ind2 != -1) return [ 0, 0, kern.rval[ind1].vals[ind2], 0 ]; } } return [ 0, 0, 0, 0 ]; }; var gls = []; for (var i = 0; i < str.length; i++) { var cc = str.codePointAt(i); if (cc > 65535) i++; gls.push(Typr["U"]["codeToGlyph"](font, cc)); } var shape = []; for (var i = 0; i < gls.length; i++) { var padj = getGlyphPosition(font, gls, i); var gid = gls[i]; var ax = font["hmtx"].aWidth[gid] + padj[2]; shape.push({ g: gid, cl: i, dx: 0, dy: 0, ax: ax, ay: 0 }); } return shape; }, shapeToPath: function(font, shape, clr) { var tpath = { cmds: [], crds: [] }; var x = 0, y = 0; for (var i = 0; i < shape.length; i++) { var it = shape[i]; var path = Typr["U"]["glyphToPath"](font, it["g"]), crds = path["crds"]; for (var j = 0; j < crds.length; j += 2) { tpath.crds.push(crds[j] + x + it["dx"]); tpath.crds.push(crds[j + 1] + y + it["dy"]); } if (clr) tpath.cmds.push(clr); for (var j = 0; j < path["cmds"].length; j++) tpath.cmds.push(path["cmds"][j]); var clen = tpath.cmds.length; if (clr) if (clen != 0 && tpath.cmds[clen - 1] != "X") tpath.cmds.push("X"); x += it["ax"]; y += it["ay"]; } return { cmds: tpath.cmds, crds: tpath.crds }; }, codeToGlyph: function(font, code) { var cmap = font["cmap"]; var tind = -1, pps = [ "p3e10", "p0e4", "p3e1", "p1e0", "p0e3", "p0e1" ]; for (var i = 0; i < pps.length; i++) if (cmap.ids[pps[i]] != null) { tind = cmap.ids[pps[i]]; break; } if (tind == -1) throw "no familiar platform and encoding!"; var arrSearch = function(arr, k, v) { var l = 0, r = Math.floor(arr.length / k); while (l + 1 != r) { var mid = l + (r - l >>> 1); if (arr[mid * k] <= v) l = mid; else r = mid; } return l * k; }; var tab = cmap.tables[tind], fmt = tab.format, gid = -1; if (fmt == 0) { if (code >= tab.map.length) gid = 0; else gid = tab.map[code]; } else if (fmt == 4) { var sind = -1, ec = tab.endCount; if (code > ec[ec.length - 1]) sind = -1; else { sind = arrSearch(ec, 1, code); if (ec[sind] < code) sind++; } if (sind == -1) gid = 0; else if (code < tab.startCount[sind]) gid = 0; else { var gli = 0; if (tab.idRangeOffset[sind] != 0) gli = tab.glyphIdArray[code - tab.startCount[sind] + (tab.idRangeOffset[sind] >> 1) - (tab.idRangeOffset.length - sind)]; else gli = code + tab.idDelta[sind]; gid = gli & 65535; } } else if (fmt == 6) { var off = code - tab.firstCode, arr = tab.glyphIdArray; if (off < 0 || off >= arr.length) gid = 0; else gid = arr[off]; } else if (fmt == 12) { var grp = tab.groups; if (code > grp[grp.length - 2]) gid = 0; else { var i = arrSearch(grp, 3, code); if (grp[i] <= code && code <= grp[i + 1]) { gid = grp[i + 2] + (code - grp[i]); } if (gid == -1) gid = 0; } } else throw "unknown cmap table format " + tab.format; var SVG = font["SVG "], loca = font["loca"]; if (gid != 0 && font["CFF "] == null && (SVG == null || SVG.entries[gid] == null) && loca[gid] == loca[gid + 1] && [ 9, 10, 11, 12, 13, 32, 133, 160, 5760, 8232, 8233, 8239, 12288, 6158, 8203, 8204, 8205, 8288, 65279 ].indexOf(code) == -1 && !(8192 <= code && code <= 8202)) gid = 0; return gid; }, glyphToPath: function(font, gid) { var path = { cmds: [], crds: [] }; var SVG = font["SVG "], CFF = font["CFF "]; var U = Typr["U"]; if (SVG && SVG.entries[gid]) { var p = SVG.entries[gid]; if (p != null) { if (typeof p == "string") { p = U["SVG"].toPath(p); SVG.entries[gid] = p; } path = p; } } else if (CFF) { var pdct = CFF["Private"]; var state = { x: 0, y: 0, stack: [], nStems: 0, haveWidth: false, width: pdct ? pdct["defaultWidthX"] : 0, open: false }; if (CFF["ROS"]) { var gi = 0; while (CFF["FDSelect"][gi + 2] <= gid) gi += 2; pdct = CFF["FDArray"][CFF["FDSelect"][gi + 1]]["Private"]; } U["_drawCFF"](CFF["CharStrings"][gid], state, CFF, pdct, path); } else if (font["glyf"]) { U["_drawGlyf"](gid, font, path); } return { cmds: path.cmds, crds: path.crds }; }, _drawGlyf: function(gid, font, path) { var gl = font["glyf"][gid]; if (gl == null) gl = font["glyf"][gid] = Typr["T"].glyf._parseGlyf(font, gid); if (gl != null) { if (gl.noc > -1) Typr["U"]["_simpleGlyph"](gl, path); else Typr["U"]["_compoGlyph"](gl, font, path); } }, _simpleGlyph: function(gl, p) { var P = Typr["U"]["P"]; for (var c = 0; c < gl.noc; c++) { var i0 = c == 0 ? 0 : gl.endPts[c - 1] + 1; var il = gl.endPts[c]; for (var i = i0; i <= il; i++) { var pr = i == i0 ? il : i - 1; var nx = i == il ? i0 : i + 1; var onCurve = gl.flags[i] & 1; var prOnCurve = gl.flags[pr] & 1; var nxOnCurve = gl.flags[nx] & 1; var x = gl.xs[i], y = gl.ys[i]; if (i == i0) { if (onCurve) { if (prOnCurve) P.MoveTo(p, gl.xs[pr], gl.ys[pr]); else { P.MoveTo(p, x, y); continue; } } else { if (prOnCurve) P.MoveTo(p, gl.xs[pr], gl.ys[pr]); else P.MoveTo(p, Math.floor((gl.xs[pr] + x) * .5), Math.floor((gl.ys[pr] + y) * .5)); } } if (onCurve) { if (prOnCurve) P.LineTo(p, x, y); } else { if (nxOnCurve) P.qCurveTo(p, x, y, gl.xs[nx], gl.ys[nx]); else P.qCurveTo(p, x, y, Math.floor((x + gl.xs[nx]) * .5), Math.floor((y + gl.ys[nx]) * .5)); } } P.ClosePath(p); } }, _compoGlyph: function(gl, font, p) { for (var j = 0; j < gl.parts.length; j++) { var path = { cmds: [], crds: [] }; var prt = gl.parts[j]; Typr["U"]["_drawGlyf"](prt.glyphIndex, font, path); var m = prt.m; for (var i = 0; i < path.crds.length; i += 2) { var x = path.crds[i], y = path.crds[i + 1]; p.crds.push(x * m.a + y * m.b + m.tx); p.crds.push(x * m.c + y * m.d + m.ty); } for (var i = 0; i < path.cmds.length; i++) p.cmds.push(path.cmds[i]); } }, pathToSVG: function(path, prec) { var cmds = path["cmds"], crds = path["crds"]; if (prec == null) prec = 5; var out = [], co = 0, lmap = { M: 2, L: 2, Q: 4, C: 6 }; for (var i = 0; i < cmds.length; i++) { var cmd = cmds[i], cn = co + (lmap[cmd] ? lmap[cmd] : 0); out.push(cmd); while (co < cn) { var c = crds[co++]; out.push(parseFloat(c.toFixed(prec)) + (co == cn ? "" : " ")); } } return out.join(""); }, SVGToPath: function(d) { var pth = { cmds: [], crds: [] }; Typr["U"]["SVG"].svgToPath(d, pth); return { cmds: pth.cmds, crds: pth.crds }; }, pathToContext: function(path, ctx) { var c = 0, cmds = path["cmds"], crds = path["crds"]; for (var j = 0; j < cmds.length; j++) { var cmd = cmds[j]; if (cmd == "M") { ctx.moveTo(crds[c], crds[c + 1]); c += 2; } else if (cmd == "L") { ctx.lineTo(crds[c], crds[c + 1]); c += 2; } else if (cmd == "C") { ctx.bezierCurveTo(crds[c], crds[c + 1], crds[c + 2], crds[c + 3], crds[c + 4], crds[c + 5]); c += 6; } else if (cmd == "Q") { ctx.quadraticCurveTo(crds[c], crds[c + 1], crds[c + 2], crds[c + 3]); c += 4; } else if (cmd.charAt(0) == "#") { ctx.beginPath(); ctx.fillStyle = cmd; } else if (cmd == "Z") { ctx.closePath(); } else if (cmd == "X") { ctx.fill(); } } }, P: { MoveTo: function(p, x, y) { p.cmds.push("M"); p.crds.push(x, y); }, LineTo: function(p, x, y) { p.cmds.push("L"); p.crds.push(x, y); }, CurveTo: function(p, a, b, c, d, e, f) { p.cmds.push("C"); p.crds.push(a, b, c, d, e, f); }, qCurveTo: function(p, a, b, c, d) { p.cmds.push("Q"); p.crds.push(a, b, c, d); }, ClosePath: function(p) { p.cmds.push("Z"); } }, _drawCFF: function(cmds, state, font, pdct, p) { var stack = state.stack; var nStems = state.nStems, haveWidth = state.haveWidth, width = state.width, open = state.open; var i = 0; var x = state.x, y = state.y, c1x = 0, c1y = 0, c2x = 0, c2y = 0, c3x = 0, c3y = 0, c4x = 0, c4y = 0, jpx = 0, jpy = 0; var CFF = Typr["T"].CFF, P = Typr["U"]["P"]; var nominalWidthX = pdct["nominalWidthX"]; var o = { val: 0, size: 0 }; while (i < cmds.length) { CFF.getCharString(cmds, i, o); var v = o.val; i += o.size; if (v == "o1" || v == "o18") { var hasWidthArg; hasWidthArg = stack.length % 2 !== 0; if (hasWidthArg && !haveWidth) { width = stack.shift() + nominalWidthX; } nStems += stack.length >> 1; stack.length = 0; haveWidth = true; } else if (v == "o3" || v == "o23") { var hasWidthArg; hasWidthArg = stack.length % 2 !== 0; if (hasWidthArg && !haveWidth) { width = stack.shift() + nominalWidthX; } nStems += stack.length >> 1; stack.length = 0; haveWidth = true; } else if (v == "o4") { if (stack.length > 1 && !haveWidth) { width = stack.shift() + nominalWidthX; haveWidth = true; } if (open) P.ClosePath(p); y += stack.pop(); P.MoveTo(p, x, y); open = true; } else if (v == "o5") { while (stack.length > 0) { x += stack.shift(); y += stack.shift(); P.LineTo(p, x, y); } } else if (v == "o6" || v == "o7") { var count = stack.length; var isX = v == "o6"; for (var j = 0; j < count; j++) { var sval = stack.shift(); if (isX) x += sval; else y += sval; isX = !isX; P.LineTo(p, x, y); } } else if (v == "o8" || v == "o24") { var count = stack.length; var index = 0; while (index + 6 <= count) { c1x = x + stack.shift(); c1y = y + stack.shift(); c2x = c1x + stack.shift(); c2y = c1y + stack.shift(); x = c2x + stack.shift(); y = c2y + stack.shift(); P.CurveTo(p, c1x, c1y, c2x, c2y, x, y); index += 6; } if (v == "o24") { x += stack.shift(); y += stack.shift(); P.LineTo(p, x, y); } } else if (v == "o11") break; else if (v == "o1234" || v == "o1235" || v == "o1236" || v == "o1237") { if (v == "o1234") { c1x = x + stack.shift(); c1y = y; c2x = c1x + stack.shift(); c2y = c1y + stack.shift(); jpx = c2x + stack.shift(); jpy = c2y; c3x = jpx + stack.shift(); c3y = c2y; c4x = c3x + stack.shift(); c4y = y; x = c4x + stack.shift(); P.CurveTo(p, c1x, c1y, c2x, c2y, jpx, jpy); P.CurveTo(p, c3x, c3y, c4x, c4y, x, y); } if (v == "o1235") { c1x = x + stack.shift(); c1y = y + stack.shift(); c2x = c1x + stack.shift(); c2y = c1y + stack.shift(); jpx = c2x + stack.shift(); jpy = c2y + stack.shift(); c3x = jpx + stack.shift(); c3y = jpy + stack.shift(); c4x = c3x + stack.shift(); c4y = c3y + stack.shift(); x = c4x + stack.shift(); y = c4y + stack.shift(); stack.shift(); P.CurveTo(p, c1x, c1y, c2x, c2y, jpx, jpy); P.CurveTo(p, c3x, c3y, c4x, c4y, x, y); } if (v == "o1236") { c1x = x + stack.shift(); c1y = y + stack.shift(); c2x = c1x + stack.shift(); c2y = c1y + stack.shift(); jpx = c2x + stack.shift(); jpy = c2y; c3x = jpx + stack.shift(); c3y = c2y; c4x = c3x + stack.shift(); c4y = c3y + stack.shift(); x = c4x + stack.shift(); P.CurveTo(p, c1x, c1y, c2x, c2y, jpx, jpy); P.CurveTo(p, c3x, c3y, c4x, c4y, x, y); } if (v == "o1237") { c1x = x + stack.shift(); c1y = y + stack.shift(); c2x = c1x + stack.shift(); c2y = c1y + stack.shift(); jpx = c2x + stack.shift(); jpy = c2y + stack.shift(); c3x = jpx + stack.shift(); c3y = jpy + stack.shift(); c4x = c3x + stack.shift(); c4y = c3y + stack.shift(); if (Math.abs(c4x - x) > Math.abs(c4y - y)) { x = c4x + stack.shift(); } else { y = c4y + stack.shift(); } P.CurveTo(p, c1x, c1y, c2x, c2y, jpx, jpy); P.CurveTo(p, c3x, c3y, c4x, c4y, x, y); } } else if (v == "o14") { if (stack.length > 0 && !haveWidth) { width = stack.shift() + font["nominalWidthX"]; haveWidth = true; } if (stack.length == 4) { var adx = stack.shift(); var ady = stack.shift(); var bchar = stack.shift(); var achar = stack.shift(); var bind = CFF.glyphBySE(font, bchar); var aind = CFF.glyphBySE(font, achar); Typr["U"]["_drawCFF"](font["CharStrings"][bind], state, font, pdct, p); state.x = adx; state.y = ady; Typr["U"]["_drawCFF"](font["CharStrings"][aind], state, font, pdct, p); } if (open) { P.ClosePath(p); open = false; } } else if (v == "o19" || v == "o20") { var hasWidthArg; hasWidthArg = stack.length % 2 !== 0; if (hasWidthArg && !haveWidth) { width = stack.shift() + nominalWidthX; } nStems += stack.length >> 1; stack.length = 0; haveWidth = true; i += nStems + 7 >> 3; } else if (v == "o21") { if (stack.length > 2 && !haveWidth) { width = stack.shift() + nominalWidthX; haveWidth = true; } y += stack.pop(); x += stack.pop(); if (open) P.ClosePath(p); P.MoveTo(p, x, y); open = true; } else if (v == "o22") { if (stack.length > 1 && !haveWidth) { width = stack.shift() + nominalWidthX; haveWidth = true; } x += stack.pop(); if (open) P.ClosePath(p); P.MoveTo(p, x, y); open = true; } else if (v == "o25") { while (stack.length > 6) { x += stack.shift(); y += stack.shift(); P.LineTo(p, x, y); } c1x = x + stack.shift(); c1y = y + stack.shift(); c2x = c1x + stack.shift(); c2y = c1y + stack.shift(); x = c2x + stack.shift(); y = c2y + stack.shift(); P.CurveTo(p, c1x, c1y, c2x, c2y, x, y); } else if (v == "o26") { if (stack.length % 2) { x += stack.shift(); } while (stack.length > 0) { c1x = x; c1y = y + stack.shift(); c2x = c1x + stack.shift(); c2y = c1y + stack.shift(); x = c2x; y = c2y + stack.shift(); P.CurveTo(p, c1x, c1y, c2x, c2y, x, y); } } else if (v == "o27") { if (stack.length % 2) { y += stack.shift(); } while (stack.length > 0) { c1x = x + stack.shift(); c1y = y; c2x = c1x + stack.shift(); c2y = c1y + stack.shift(); x = c2x + stack.shift(); y = c2y; P.CurveTo(p, c1x, c1y, c2x, c2y, x, y); } } else if (v == "o10" || v == "o29") { var obj = v == "o10" ? pdct : font; if (stack.length == 0) { console.log("error: empty stack"); } else { var ind = stack.pop(); var subr = obj["Subrs"][ind + obj["Bias"]]; state.x = x; state.y = y; state.nStems = nStems; state.haveWidth = haveWidth; state.width = width; state.open = open; Typr["U"]["_drawCFF"](subr, state, font, pdct, p); x = state.x; y = state.y; nStems = state.nStems; haveWidth = state.haveWidth; width = state.width; open = state.open; } } else if (v == "o30" || v == "o31") { var count, count1 = stack.length; var index = 0; var alternate = v == "o31"; count = count1 & ~2; index += count1 - count; while (index < count) { if (alternate) { c1x = x + stack.shift(); c1y = y; c2x = c1x + stack.shift(); c2y = c1y + stack.shift(); y = c2y + stack.shift(); if (count - index == 5) { x = c2x + stack.shift(); index++; } else x = c2x; alternate = false; } else { c1x = x; c1y = y + stack.shift(); c2x = c1x + stack.shift(); c2y = c1y + stack.shift(); x = c2x + stack.shift(); if (count - index == 5) { y = c2y + stack.shift(); index++; } else y = c2y; alternate = true; } P.CurveTo(p, c1x, c1y, c2x, c2y, x, y); index += 4; } } else if ((v + "").charAt(0) == "o") { console.log("Unknown operation: " + v, cmds); throw v; } else stack.push(v); } state.x = x; state.y = y; state.nStems = nStems; state.haveWidth = haveWidth; state.width = width; state.open = open; }, SVG: function() { var M = { getScale: function(m) { return Math.sqrt(Math.abs(m[0] * m[3] - m[1] * m[2])); }, translate: function(m, x, y) { M.concat(m, [ 1, 0, 0, 1, x, y ]); }, rotate: function(m, a) { M.concat(m, [ Math.cos(a), -Math.sin(a), Math.sin(a), Math.cos(a), 0, 0 ]); }, scale: function(m, x, y) { M.concat(m, [ x, 0, 0, y, 0, 0 ]); }, concat: function(m, w) { var a = m[0], b = m[1], c = m[2], d = m[3], tx = m[4], ty = m[5]; m[0] = a * w[0] + b * w[2]; m[1] = a * w[1] + b * w[3]; m[2] = c * w[0] + d * w[2]; m[3] = c * w[1] + d * w[3]; m[4] = tx * w[0] + ty * w[2] + w[4]; m[5] = tx * w[1] + ty * w[3] + w[5]; }, invert: function(m) { var a = m[0], b = m[1], c = m[2], d = m[3], tx = m[4], ty = m[5], adbc = a * d - b * c; m[0] = d / adbc; m[1] = -b / adbc; m[2] = -c / adbc; m[3] = a / adbc; m[4] = (c * ty - d * tx) / adbc; m[5] = (b * tx - a * ty) / adbc; }, multPoint: function(m, p) { var x = p[0], y = p[1]; return [ x * m[0] + y * m[2] + m[4], x * m[1] + y * m[3] + m[5] ]; }, multArray: function(m, a) { for (var i = 0; i < a.length; i += 2) { var x = a[i], y = a[i + 1]; a[i] = x * m[0] + y * m[2] + m[4]; a[i + 1] = x * m[1] + y * m[3] + m[5]; } } }; function _bracketSplit(str, lbr, rbr) { var out = [], pos = 0, ci = 0, lvl = 0; while (true) { var li = str.indexOf(lbr, ci); var ri = str.indexOf(rbr, ci); if (li == -1 && ri == -1) break; if (ri == -1 || li != -1 && li < ri) { if (lvl == 0) { out.push(str.slice(pos, li).trim()); pos = li + 1; } lvl++; ci = li + 1; } else if (li == -1 || ri != -1 && ri < li) { lvl--; if (lvl == 0) { out.push(str.slice(pos, ri).trim()); pos = ri + 1; } ci = ri + 1; } } return out; } function cssMap(str) { var pts = _bracketSplit(str, "{", "}"); var css = {}; for (var i = 0; i < pts.length; i += 2) { var cn = pts[i].split(","); for (var j = 0; j < cn.length; j++) { var cnj = cn[j].trim(); if (css[cnj] == null) css[cnj] = ""; css[cnj] += pts[i + 1]; } } return css; } function readTrnf(trna) { var pts = _bracketSplit(trna, "(", ")"); var m = [ 1, 0, 0, 1, 0, 0 ]; for (var i = 0; i < pts.length; i += 2) { var om = m; m = _readTrnsAttr(pts[i], pts[i + 1]); M.concat(m, om); } return m; } function _readTrnsAttr(fnc, vls) { var m = [ 1, 0, 0, 1, 0, 0 ], gotSep = true; for (var i = 0; i < vls.length; i++) { var ch = vls.charAt(i); if (ch == "," || ch == " ") gotSep = true; else if (ch == ".") { if (!gotSep) { vls = vls.slice(0, i) + "," + vls.slice(i); i++; } gotSep = false; } else if (ch == "-" && i > 0 && vls[i - 1] != "e") { vls = vls.slice(0, i) + " " + vls.slice(i); i++; gotSep = true; } } vls = vls.split(/\s*[\s,]\s*/).map(parseFloat); if (fnc == "translate") { if (vls.length == 1) M.translate(m, vls[0], 0); else M.translate(m, vls[0], vls[1]); } else if (fnc == "scale") { if (vls.length == 1) M.scale(m, vls[0], vls[0]); else M.scale(m, vls[0], vls[1]); } else if (fnc == "rotate") { var tx = 0, ty = 0; if (vls.length != 1) { tx = vls[1]; ty = vls[2]; } M.translate(m, -tx, -ty); M.rotate(m, -Math.PI * vls[0] / 180); M.translate(m, tx, ty); } else if (fnc == "matrix") m = vls; else console.log("unknown transform: ", fnc); return m; } function toPath(str) { var pth = { cmds: [], crds: [] }; if (str == null) return pth; var prsr = new DOMParser(); var doc = prsr["parseFromString"](str, "image/svg+xml"); var svg = doc.getElementsByTagName("svg")[0]; var vb = svg.getAttribute("viewBox"); if (vb) vb = vb.trim().split(" ").map(parseFloat); else vb = [ 0, 0, 1e3, 1e3 ]; _toPath(svg.children, pth); for (var i = 0; i < pth.crds.length; i += 2) { var x = pth.crds[i], y = pth.crds[i + 1]; x -= vb[0]; y -= vb[1]; y = -y; pth.crds[i] = x; pth.crds[i + 1] = y; } return pth; } function _toPath(nds, pth, fill) { for (var ni = 0; ni < nds.length; ni++) { var nd = nds[ni], tn = nd.tagName; var cfl = nd.getAttribute("fill"); if (cfl == null) cfl = fill; if (tn == "g") { var tp = { crds: [], cmds: [] }; _toPath(nd.children, tp, cfl); var trf = nd.getAttribute("transform"); if (trf) { var m = readTrnf(trf); M.multArray(m, tp.crds); } pth.crds = pth.crds.concat(tp.crds); pth.cmds = pth.cmds.concat(tp.cmds); } else if (tn == "path" || tn == "circle" || tn == "ellipse") { pth.cmds.push(cfl ? cfl : "#000000"); var d; if (tn == "path") d = nd.getAttribute("d"); if (tn == "circle" || tn == "ellipse") { var vls = [ 0, 0, 0, 0 ], nms = [ "cx", "cy", "rx", "ry", "r" ]; for (var i = 0; i < 5; i++) { var V = nd.getAttribute(nms[i]); if (V) { V = parseFloat(V); if (i < 4) vls[i] = V; else vls[2] = vls[3] = V; } } var cx = vls[0], cy = vls[1], rx = vls[2], ry = vls[3]; d = [ "M", cx - rx, cy, "a", rx, ry, 0, 1, 0, rx * 2, 0, "a", rx, ry, 0, 1, 0, -rx * 2, 0 ].join(" "); } svgToPath(d, pth); pth.cmds.push("X"); } else if (tn == "defs"); else console.log(tn, nd); } } function _tokens(d) { var ts = [], off = 0, rn = false, cn = "", pc = ""; while (off < d.length) { var cc = d.charCodeAt(off), ch = d.charAt(off); off++; var isNum = 48 <= cc && cc <= 57 || ch == "." || ch == "-" || ch == "e" || ch == "E"; if (rn) { if (ch == "-" && pc != "e" || ch == "." && cn.indexOf(".") != -1) { ts.push(parseFloat(cn)); cn = ch; } else if (isNum) cn += ch; else { ts.push(parseFloat(cn)); if (ch != "," && ch != " ") ts.push(ch); rn = false; } } else { if (isNum) { cn = ch; rn = true; } else if (ch != "," && ch != " ") ts.push(ch); } pc = ch; } if (rn) ts.push(parseFloat(cn)); return ts; } function _reps(ts, off, ps) { var i = off; while (i < ts.length) { if (typeof ts[i] == "string") break; i += ps; } return (i - off) / ps; } function svgToPath(d, pth) { var ts = _tokens(d); var i = 0, x = 0, y = 0, ox = 0, oy = 0, oldo = pth.crds.length; var pc = { M: 2, L: 2, H: 1, V: 1, T: 2, S: 4, A: 7, Q: 4, C: 6 }; var cmds = pth.cmds, crds = pth.crds; while (i < ts.length) { var cmd = ts[i]; i++; var cmu = cmd.toUpperCase(); if (cmu == "Z") { cmds.push("Z"); x = ox; y = oy; } else { var ps = pc[cmu], reps = _reps(ts, i, ps); for (var j = 0; j < reps; j++) { if (j == 1 && cmu == "M") { cmd = cmd == cmu ? "L" : "l"; cmu = "L"; } var xi = 0, yi = 0; if (cmd != cmu) { xi = x; yi = y; } if (cmu == "M") { x = xi + ts[i++]; y = yi + ts[i++]; cmds.push("M"); crds.push(x, y); ox = x; oy = y; } else if (cmu == "L") { x = xi + ts[i++]; y = yi + ts[i++]; cmds.push("L"); crds.push(x, y); } else if (cmu == "H") { x = xi + ts[i++]; cmds.push("L"); crds.push(x, y); } else if (cmu == "V") { y = yi + ts[i++]; cmds.push("L"); crds.push(x, y); } else if (cmu == "Q") { var x1 = xi + ts[i++], y1 = yi + ts[i++], x2 = xi + ts[i++], y2 = yi + ts[i++]; cmds.push("Q"); crds.push(x1, y1, x2, y2); x = x2; y = y2; } else if (cmu == "T") { var co = Math.max(crds.length - 2, oldo); var x1 = x + x - crds[co], y1 = y + y - crds[co + 1]; var x2 = xi + ts[i++], y2 = yi + ts[i++]; cmds.push("Q"); crds.push(x1, y1, x2, y2); x = x2; y = y2; } else if (cmu == "C") { var x1 = xi + ts[i++], y1 = yi + ts[i++], x2 = xi + ts[i++], y2 = yi + ts[i++], x3 = xi + ts[i++], y3 = yi + ts[i++]; cmds.push("C"); crds.push(x1, y1, x2, y2, x3, y3); x = x3; y = y3; } else if (cmu == "S") { var co = Math.max(crds.length - (cmds[cmds.length - 1] == "C" ? 4 : 2), oldo); var x1 = x + x - crds[co], y1 = y + y - crds[co + 1]; var x2 = xi + ts[i++], y2 = yi + ts[i++], x3 = xi + ts[i++], y3 = yi + ts[i++]; cmds.push("C"); crds.push(x1, y1, x2, y2, x3, y3); x = x3; y = y3; } else if (cmu == "A") { var x1 = x, y1 = y; var rx = ts[i++], ry = ts[i++]; var phi = ts[i++] * (Math.PI / 180), fA = ts[i++], fS = ts[i++]; var x2 = xi + ts[i++], y2 = yi + ts[i++]; if (x2 == x && y2 == y && rx == 0 && ry == 0) continue; var hdx = (x1 - x2) / 2, hdy = (y1 - y2) / 2; var cosP = Math.cos(phi), sinP = Math.sin(phi); var x1A = cosP * hdx + sinP * hdy; var y1A = -sinP * hdx + cosP * hdy; var rxS = rx * rx, ryS = ry * ry; var x1AS = x1A * x1A, y1AS = y1A * y1A; var frc = (rxS * ryS - rxS * y1AS - ryS * x1AS) / (rxS * y1AS + ryS * x1AS); var coef = (fA != fS ? 1 : -1) * Math.sqrt(Math.max(frc, 0)); var cxA = coef * (rx * y1A) / ry; var cyA = -coef * (ry * x1A) / rx; var cx = cosP * cxA - sinP * cyA + (x1 + x2) / 2; var cy = sinP * cxA + cosP * cyA + (y1 + y2) / 2; var angl = function(ux, uy, vx, vy) { var lU = Math.sqrt(ux * ux + uy * uy), lV = Math.sqrt(vx * vx + vy * vy); var num = (ux * vx + uy * vy) / (lU * lV); return (ux * vy - uy * vx >= 0 ? 1 : -1) * Math.acos(Math.max(-1, Math.min(1, num))); }; var vX = (x1A - cxA) / rx, vY = (y1A - cyA) / ry; var theta1 = angl(1, 0, vX, vY); var dtheta = angl(vX, vY, (-x1A - cxA) / rx, (-y1A - cyA) / ry); dtheta = dtheta % (2 * Math.PI); var arc = function(gst, x, y, r, a0, a1, neg) { var rotate = function(m, a) { var si = Math.sin(a), co = Math.cos(a); var a = m[0], b = m[1], c = m[2], d = m[3]; m[0] = a * co + b * si; m[1] = -a * si + b * co; m[2] = c * co + d * si; m[3] = -c * si + d * co; }; var multArr = function(m, a) { for (var j = 0; j < a.length; j += 2) { var x = a[j], y = a[j + 1]; a[j] = m[0] * x + m[2] * y + m[4]; a[j + 1] = m[1] * x + m[3] * y + m[5]; } }; var concatA = function(a, b) { for (var j = 0; j < b.length; j++) a.push(b[j]); }; var concatP = function(p, r) { concatA(p.cmds, r.cmds); concatA(p.crds, r.crds); }; if (neg) while (a1 > a0) a1 -= 2 * Math.PI; else while (a1 < a0) a1 += 2 * Math.PI; var th = (a1 - a0) / 4; var x0 = Math.cos(th / 2), y0 = -Math.sin(th / 2); var x1 = (4 - x0) / 3, y1 = y0 == 0 ? y0 : (1 - x0) * (3 - x0) / (3 * y0); var x2 = x1, y2 = -y1; var x3 = x0, y3 = -y0; var ps = [ x1, y1, x2, y2, x3, y3 ]; var pth = { cmds: [ "C", "C", "C", "C" ], crds: ps.slice(0) }; var rot = [ 1, 0, 0, 1, 0, 0 ]; rotate(rot, -th); for (var j = 0; j < 3; j++) { multArr(rot, ps); concatA(pth.crds, ps); } rotate(rot, -a0 + th / 2); rot[0] *= r; rot[1] *= r; rot[2] *= r; rot[3] *= r; rot[4] = x; rot[5] = y; multArr(rot, pth.crds); multArr(gst.ctm, pth.crds); concatP(gst.pth, pth); }; var gst = { pth: pth, ctm: [ rx * cosP, rx * sinP, -ry * sinP, ry * cosP, cx, cy ] }; arc(gst, 0, 0, 1, theta1, theta1 + dtheta, fS == 0); x = x2; y = y2; } else console.log("Unknown SVG command " + cmd); } } } } return { cssMap: cssMap, readTrnf: readTrnf, svgToPath: svgToPath, toPath: toPath }; }(), initHB: function(hurl, resp) { var codeLength = function(code) { var len = 0; if ((code & 4294967295 - (1 << 7) + 1) == 0) { len = 1; } else if ((code & 4294967295 - (1 << 11) + 1) == 0) { len = 2; } else if ((code & 4294967295 - (1 << 16) + 1) == 0) { len = 3; } else if ((code & 4294967295 - (1 << 21) + 1) == 0) { len = 4; } return len; }; var te = new window["TextEncoder"]("utf8"); fetch(hurl).then(function(x) { return x["arrayBuffer"](); }).then(function(ab) { return WebAssembly["instantiate"](ab); }).then(function(res) { console.log("HB ready"); var exp = res["instance"]["exports"], mem = exp["memory"]; mem["grow"](700); var heapu8 = new Uint8Array(mem.buffer); var u32 = new Uint32Array(mem.buffer); var i32 = new Int32Array(mem.buffer); var __lastFnt, blob, blobPtr, face, font; Typr["U"]["shapeHB"] = function() { var toJson = function(ptr) { var length = exp["hb_buffer_get_length"](ptr); var result = []; var iPtr32 = exp["hb_buffer_get_glyph_infos"](ptr, 0) >>> 2; var pPtr32 = exp["hb_buffer_get_glyph_positions"](ptr, 0) >>> 2; for (var i = 0; i < length; ++i) { var a = iPtr32 + i * 5, b = pPtr32 + i * 5; result.push({ g: u32[a + 0], cl: u32[a + 2], ax: i32[b + 0], ay: i32[b + 1], dx: i32[b + 2], dy: i32[b + 3] }); } return result; }; return function(fnt, str, ltr) { var fdata = fnt["_data"], fn = fnt["name"]["postScriptName"]; if (__lastFnt != fn) { if (blob != null) { exp["hb_blob_destroy"](blob); exp["free"](blobPtr); exp["hb_face_destroy"](face); exp["hb_font_destroy"](font); } blobPtr = exp["malloc"](fdata.byteLength); heapu8.set(fdata, blobPtr); blob = exp["hb_blob_create"](blobPtr, fdata.byteLength, 2, 0, 0); face = exp["hb_face_create"](blob, 0); font = exp["hb_font_create"](face); __lastFnt = fn; } var buffer = exp["hb_buffer_create"](); var bytes = te["encode"](str); var len = bytes.length, strp = exp["malloc"](len); heapu8.set(bytes, strp); exp["hb_buffer_add_utf8"](buffer, strp, len, 0, len); exp["free"](strp); exp["hb_buffer_set_direction"](buffer, ltr ? 4 : 5); exp["hb_buffer_guess_segment_properties"](buffer); exp["hb_shape"](font, buffer, 0, 0); var json = toJson(buffer); exp["hb_buffer_destroy"](buffer); var arr = json.slice(0); if (!ltr) arr.reverse(); var ci = 0, bi = 0; for (var i = 1; i < arr.length; i++) { var gl = arr[i], cl = gl["cl"]; while (true) { var cpt = str.codePointAt(ci), cln = codeLength(cpt); if (bi + cln <= cl) { bi += cln; ci += cpt <= 65535 ? 1 : 2; } else break; } gl["cl"] = ci; } return json; }; }(); resp(); }); } }; const QQ_GROUP = [ "854137118" ]; var _self = unsafeWindow; var top = _self; var UE$1; var modelId = "modelId_xx"; const selfintv = setInterval(() => { if (unsafeWindow) { _self = unsafeWindow; top = _self; UE$1 = _self.UE; try { reportOnline(); String.prototype.replaceAll = function(s1, s2) { return this.replace(new RegExp(s1, "gm"), s2); }; while (top !== _self.top) { top = top.parent.document ? top.parent : _self.top; if (top.location.pathname === "/mycourse/studentstudy") break; } } catch (err) { top = _self; } clearInterval(selfintv); } }, GLOBAL.delay); function checkVersion() { function compare(v1 = "0", v2 = "0") { v1 = String(v1).split("."); v2 = String(v2).split("."); const minVersionLens = Math.min(v1.length, v2.length); let result = 0; for (let i = 0; i < minVersionLens; i++) { const curV1 = Number(v1[i]); const curV2 = Number(v2[i]); if (curV1 > curV2) { result = 1; break; } else if (curV1 < curV2) { result = -1; break; } } if (result === 0 && v1.length !== v2.length) { const v1BiggerThenv2 = v1.length > v2.length; const maxLensVersion = v1BiggerThenv2 ? v1 : v2; for (let i = minVersionLens; i < maxLensVersion.length; i++) { const curVersion = Number(maxLensVersion[i]); if (curVersion > 0) { v1BiggerThenv2 ? result = 1 : result = -1; break; } } } return result; } gmRequest({ method: "GET", url: "https://greasyfork.org/en/scripts/451356.json", timeout: GLOBAL.timeout, onload: function(r) { const obj = JSON.parse(r.responseText); if (obj.name === GM_info.script.name && compare(obj.version, GM_info.script.version) === 1 && new Date(obj.code_updated_at).getTime() + 1e3 * 60 * 60 * 2 < new Date().getTime()) { iframeMsg("update", { v1: GM_info.script.version, v2: obj.version, href: obj.url }); } } }); } top.addEventListener("message", event => { if (event.data.type === "jump") { GLOBAL.index++; iframeMsg("tip", { tip: "准备答第" + (GLOBAL.index + 1) + "题" }); } else if (event.data.type === "stop") { GLOBAL.stop = event.data.val; } else if (event.data.type === "auto_jump") { GM_setValue("auto_jump", event.data.flag); iframeMsg("tip", { tip: "已" + (event.data.flag ? "开启" : "关闭") + "自动切换,页面刷新后生效" }); } else if (event.data.type === "exam_auto_save") { GM_setValue("exam_auto_save", event.data.flag); iframeMsg("tip", { tip: "已" + (event.data.flag ? "开启" : "关闭") + "答题完成后自动保存" }); } else if (event.data.type === "exam_auto_submit") { GM_setValue("exam_auto_submit", event.data.flag); iframeMsg("tip", { tip: "已" + (event.data.flag ? "开启" : "关闭") + "答题完成后自动提交" }); } else if (event.data.type === "exam_accuracy") { const acc = Math.min(100, Math.max(0, parseInt(event.data.val, 10) || 0)); GM_setValue("exam_accuracy", acc); iframeMsg("tip", { tip: "正确率阈值已设为 " + acc + "%" }); } else if (event.data.type === "save_setting") { GM_setValue("gpt", event.data.gpt); GM_setValue("search_delay", event.data.search_delay); GM_setValue("tiku_adapter", event.data.tiku_adapter); } }, false); $(document).keydown(function(event) { if (event.keyCode === 120 || event.key === "F9") { event.preventDefault(); if ($("#" + modelId).is(":visible")) { $("." + modelId).hide(); GM_setValue("hide", true); vm.hideTip(); } else { $("." + modelId).show(); GM_setValue("hide", false); } } else if (event.keyCode === 83) { GLOBAL.stop = true; iframeMsg("stop", GLOBAL.stop); } else if (event.keyCode === 68) { GLOBAL.stop = false; iframeMsg("stop", GLOBAL.stop); } }); function getAnswerForKey(keys, options) { return keys.map(function(val) { return options[val.charCodeAt(0) - 65]; }); } function setIntervalFunc(flag, func, time) { const interval = setInterval(() => { if (flag()) { clearInterval(interval); func(); } }, time || 1e3); } function getAnswer(str, options, type) { if (type === 0 || type === 1) { const ans = getAnswerForKey(str.match(/[A-G]/gi) || [], options); return ans.length > 0 ? ans : [ str ]; } else { return [ str ]; } } function getQuestionType(str) { if (!str) return; str = str.trim().replaceAll(/\s+/g, ""); if (TYPE[str]) return TYPE[str]; const regex = Object.keys(TYPE).join("|"); const matcher = str.match(regex); if (matcher) return TYPE[matcher[0]]; } function rand(m, n) { return Math.ceil(Math.random() * (n - m + 1) + m - 1); } const TYPE = { "阅读理解(选择)/完型填空": 66, "听力训练": 66, multichoice: 1, singlechoice: 0, SingleChoice: 0, bijudgement: 3, Judgement: 3, "单项选择题": 0, "单项选择": 0, "单选题": 0, "单选": 0, "多选": 1, "多选题": 1, "案例分析": 1, "多项选择题": 1, "多项选择": 1, "客观题": 1, "填空题": 2, "填空": 2, "对错题": 3, "判断题": 3, "判断正误": 3, "判断": 3, "主观题": 4, "问答题": 4, "简答题": 4, "名词解释": 5, "论述题": 6, "计算题": 7, "其它": 8, "分录题": 9, "资料题": 10, "连线题": 11, "排序题": 13, "完形填空": 14, "完型填空": 14, "阅读理解": 15, "口语题": 18, "听力题": 19, "A1A2题": 1, "文件作答": 4, "视频题": 1 }; function sleep(time) { return new Promise(resolve => { setTimeout(resolve, time); }); } function iframeMsg(type, message) { try { top.document.getElementById("iframeNode").contentWindow.vueDefinedProp(type, message); } catch (e) {} } function filterImg(dom) { if (location.host === "ncexam.cug.edu.cn") { String.prototype.trim = function() { return this.replace(/^\s+|\s+$/gm, ""); }; } return $(dom).clone().find("img[src]").replaceWith(function() { return $("

").text(''); }).end().find("iframe[src]").replaceWith(function() { return $("

").text('
运行日志
[00:00:00]如果此提示不消失,说明页面出现了错误,请联系作者
任务配置
如果显示视频已观看完毕,但视频任务未完成,可尝试开启复习模式或适当降低倍速后重刷
🎥 视频播放模式 💡 模拟/真实倍速分开保存 | 真实播放:0.75x~3.0x(步进0.25)(课程允许时生效)
视频倍速:
保存  |  复习模式  |  查看学习进度  |  激活挂机  |  模式: 遍历  |  视频题目: 开启 自动处理视频内题目(真实模式缓存接口答案后点选)  |  自动阅读: 开启 打开阅读界面自动阅读
章节测试:
自动答题 |  自动提交 |  自动保存 |  随机答题: 关闭
章节测试正确率(百分比):
保存   在答题正确率在规定之上并且允许自动提交时才会提交答案
视频上报间隔(秒):
保存   视频进度上报的时间间隔(秒),默认90秒,数值越小上报越频繁
考试功能:
打开考试界面后自动显示  |  多端学习: 解除 解除多端学习监控,开启此功能后可以多端学习。
题库配置

关注微信公众号:一之哥哥,发送 "token" 领取你的token,可以提高答题并发数量。

领取到token后,填入输入框中,点击保存即可。还有undefined是没有设定,而不是有效token

题库Token:
保存
自动登录配置
⚠️ 风险提示:超星官方禁止自动登录脚本,使用时可能触发账号风控。建议不要自动登录,当心风控。
自动登录: 关闭
保存登录信息
作者的话

"劝君莫惜金缕衣,劝君惜取少年时"

我闭了昏黄的台灯,此时窗外正好飘来一片月光,泼洒在电脑屏幕上,红色的报错,洁白的就像一个还未睁开眸子看着世界的孩子的心。

📁 每一次优化都是学长透支身体的结果,熬穿了不知道多少个夜晚,您的赞赏会是刺破黑暗苍穹的亮光照亮我前行的路

因为淋过雨,所以想替学弟撑把伞。学长已经把路铺好了。学长快点不起拼好饭了,赞赏助力学长全款拿下拼好饭。

✨ 有的学弟很调皮啊,给我的赞赏备注个学长CPDD,哎,说来惭愧,学长现在还没牵过女孩子的手,所以可以赞赏安慰一下学长吗?

🔧 学长自用404系列实用小工具,大家可以下载鸭

🎬 VIP追剧神器 🌙 夜间模式助手 🛠️ 破除网页限制 🫧 鼠标指针美化 📖 清晰文本助手 Live2D 看板娘 🗨️ 消息定时推送(适合有对象使用)

🎁 学长专属福利链接

东北__我们喜欢你 新_岛 我们_ 忧郁的__ __传奇

学长也还有学业在身,如果加微信未能及时回复,请多多包涵哈!学长目前准备优化:1. 添加更多免费题库 2. 兼容多平台 3. 优化脚本体验(持续优化中)4. 制作超简化版本

学长是galgame最长的河流,如果有galgame问题也欢迎大家加微信咨询:Why15236444193 | 还有我在考虑要不要建个群聊,大家可以把意愿反馈给我📩

—— 若觉得悬浮窗不舒服,可点击跳转:旧版 ——点击跳转(不要问为什么视频开倍速没用还有答题没有实时填入,因为你没有看脚本使用说明)

`; try { const _verEl = floatWin.querySelector('#xxt-version'); if (_verEl && GM_info && GM_info.script && GM_info.script.version) { _verEl.textContent = 'v' + GM_info.script.version; } } catch (e) {} // 在插入DOM之前就设好位置,避免刷新后先显示默认位置再跳动 (function() { const _pos = loadFloatPos() || xxtDefaultFloatPos(); dragCurX = _pos.x; dragCurY = _pos.y; floatWin.style.transform = `translate(${_pos.x}px, ${_pos.y}px)`; // 首帧前应用折叠状态,避免切章后先展开再缩回 if (isMinimized) { const contentEl = floatWin.querySelector('#xxt-helper-content'); if (contentEl) contentEl.style.display = 'none'; floatWin.style.height = 'auto'; floatWin.style.minHeight = 'auto'; const minProgressEl = floatWin.querySelector('#xxt-min-progress'); if (minProgressEl) minProgressEl.style.display = 'inline'; const minBtnEl = floatWin.querySelector('#xxt-min'); if (minBtnEl) { minBtnEl.textContent = '▢'; minBtnEl.title = '恢复'; } } })(); top.document.body.appendChild(floatWin); // 最小化状态在 appendChild 前已应用;initFloatingWindow 内仍保留兜底(绑事件与二次确认) // 悬浮窗样式(仅注入一次,避免异常重复挂载时 head 堆叠) const style = document.createElement("style"); style.id = 'xxt-helper-stylesheet'; style.innerHTML = ` #xxt-helper-window { position: fixed; top: 0; left: 0; width: min(720px, calc(100vw - 16px)); /* 窄屏自适应 */ height: 560px; /* 原来700px,缩小为原来的80% */ background: #fff; border: 2px solid #444; border-radius: 8px; box-shadow: 0 6px 20px rgba(0,0,0,0.25); z-index: 2147483647 !important; /* 使用最大可能的z-index值 */ display: flex; flex-direction: column; overflow: visible !important; /* 允许内容溢出 */ min-width: 240px; /* 原来300px,缩小为原来的80% */ min-height: 160px; /* 原来200px,缩小为原来的80% */ transform: translate(540px, 95px); /* 初始位置用 transform,拖动全程不触发 reflow */ will-change: transform; /* 提示浏览器提前创建合成层 */ max-width: min(720px, calc(100vw - 16px)) !important; max-height: 560px !important; /* 限制最大高度与原始高度一致 */ contain: none !important; /* 防止被CSS containment限制 */ clip: auto !important; /* 防止被裁剪 */ pointer-events: auto !important; /* 确保鼠标事件正常工作 */ visibility: visible !important; /* 确保可见性 */ opacity: 1 !important; /* 确保不透明 */ box-sizing: content-box !important; /* 确保边框不会增加元素实际大小 */ padding: 0 !important; /* 移除内边距 */ margin: 0 !important; /* 移除外边距 */ font-size: 14px !important; /* 适当缩小字体 */ } #xxt-helper-header { background: #444; color: #fff; padding: 8px 12px; cursor: move; display: flex; align-items: center; flex-shrink: 0; /* 防止头部被压缩 */ } #xxt-helper-header button { background: transparent; color: #fff; border: none; margin-left: 8px; cursor: pointer; font-size: 14px; padding: 4px 8px; border-radius: 3px; transition: background-color 0.2s; } #xxt-helper-header button:hover { background: rgba(255,255,255,0.2); } #xxt-helper-content { flex: 1; overflow: auto; /* 改为auto显示滚动条 */ padding: 15px; display: flex; flex-direction: column; z-index: 999999; /* 增加层级保证显示 */ } /* 配置标签样式 */ #config-tabs { display: flex; gap: 5px; margin-bottom: 15px; border-bottom: 2px solid #eee; flex-shrink: 0; } .config-tab { background: #f5f5f5; border: 1px solid #ddd; border-bottom: none; padding: 8px 16px; cursor: pointer; border-radius: 4px 4px 0 0; font-size: 14px; transition: all 0.2s; white-space: nowrap; } .config-tab:hover { background: #e9e9e9; } .config-tab.active { background: #fff; border-color: #007bff; color: #007bff; font-weight: bold; } /* 配置面板样式 */ .config-panel { display: none; flex: 1; overflow-y: auto; padding: 10px 0; } .config-panel.active { display: block; } /* Bootstrap面板样式优化 */ .panel { margin-bottom: 15px; border: 1px solid #ddd; border-radius: 4px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); } .panel-heading { background: #f5f5f5; border-bottom: 1px solid #ddd; padding: 10px 15px; font-weight: bold; border-radius: 4px 4px 0 0; } .panel-body { padding: 15px; } .panel-info { border-color: #bce8f1; } .panel-info > .panel-heading { background: #d9edf7; border-color: #bce8f1; color: #31708f; } /* 按钮样式优化 */ .btn { display: inline-block; padding: 6px 12px; margin-bottom: 0; font-size: 14px; font-weight: normal; line-height: 1.42857143; text-align: center; white-space: nowrap; vertical-align: middle; cursor: pointer; border: 1px solid transparent; border-radius: 4px; text-decoration: none; transition: all 0.2s; } .btn-default { color: #333; background-color: #fff; border-color: #ccc; } .btn-default:hover { color: #333; background-color: #e6e6e6; border-color: #adadad; } .btn-success { color: #fff; background-color: #5cb85c; border-color: #4cae4c; } .btn-success:hover { color: #fff; background-color: #449d44; border-color: #398439; } /* 输入框样式 */ input[type="text"], input[type="number"], input[type="password"] { display: inline-block; padding: 6px 12px; font-size: 14px; line-height: 1.42857143; color: #555; background-color: #fff; background-image: none; border: 1px solid #ccc; border-radius: 4px; box-shadow: inset 0 1px 1px rgba(0,0,0,.075); transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s; } input[type="text"]:focus, input[type="number"]:focus, input[type="password"]:focus { border-color: #66afe9; outline: 0; box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102,175,233,.6); } /* 响应式设计 */ @media (max-width: 1200px) { #xxt-helper-window { width: 640px; /* 原来800px,缩小为原来的80% */ height: 480px; /* 原来600px,缩小为原来的80% */ } } @media (max-width: 1000px) { #xxt-helper-window { width: 90vw; /* 改用视窗单位 */ height: 90vh; left: 5vw; top: 5vh; } .config-tab { padding: 6px 12px; font-size: 12px; } } /* 视频倍速输入框显示上下箭头按钮 */ #unrivalRate { /* 确保显示spinner */ -webkit-appearance: textfield !important; -moz-appearance: textfield !important; } /* 强制显示Webkit浏览器的spinner */ #unrivalRate::-webkit-outer-spin-button, #unrivalRate::-webkit-inner-spin-button { -webkit-appearance: inner-spin-button !important; opacity: 1 !important; height: 100% !important; width: 20px !important; cursor: pointer !important; display: block !important; } /* Firefox浏览器spinner样式 */ #unrivalRate[type="number"] { -moz-appearance: textfield !important; } #unrivalRate[type="number"]::-moz-number-spin-box { opacity: 1 !important; display: block !important; } .progress-panel { width: 100%; min-width: 300px; max-width: 450px; padding: 12px 15px; border-radius: 8px; background: #ffffff; box-shadow: 0 4px 12px rgba(23, 162, 184, 0.15); height: 120px; display: flex; flex-direction: column; justify-content: space-between; } .progress-header { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 4px; } .progress-label { font-size: 11px; color: #6c757d; margin-bottom: 2px; } .progress-task-name { font-size: 14px; font-weight: 600; color: #212529; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 200px; } .progress-percent { font-size: 20px; font-weight: 700; color: #17a2b8; white-space: nowrap; } .progress-meta { margin: 4px 0 6px; font-size: 11px; color: #6c757d; } .progress-bar { width: 100%; height: 14px; border-radius: 999px; background: #e9ecef; overflow: hidden; position: relative; } .progress-bar-fill { position: absolute; top: 0; left: 0; height: 100%; width: 0%; border-radius: 999px; background: linear-gradient(90deg, #0dcaf0, #0b7fab); transition: width 0.3s ease; } .progress-detail { margin-top: 6px; font-size: 11px; color: #495057; line-height: 1.4; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } `; // 将样式添加到顶级窗口 try { const topWindow = window.top || window; if (!topWindow.document.getElementById('xxt-helper-stylesheet')) { topWindow.document.head.appendChild(style); } } catch (e) { if (!document.getElementById('xxt-helper-stylesheet')) { document.head.appendChild(style); } } // 标签切换功能 function initTabSwitching() { const tabs = top.document.querySelectorAll('.config-tab'); const panels = top.document.querySelectorAll('.config-panel'); tabs.forEach(tab => { tab.addEventListener('click', () => { // 移除所有活动状态 tabs.forEach(t => t.classList.remove('active')); panels.forEach(p => p.classList.remove('active')); // 添加当前活动状态 tab.classList.add('active'); const targetPanel = top.document.getElementById(tab.dataset.tab); if (targetPanel) { targetPanel.classList.add('active'); } }); }); } // 初始化标签切换 setTimeout(initTabSwitching, 100); // 拖动逻辑 // 拖动逻辑 function setupDragging() { const header = top.document.getElementById("xxt-helper-header"); let isDown = false, startMouseX, startMouseY; header.addEventListener("pointerdown", e => { if (e.target.tagName === 'BUTTON') return; isDown = true; startMouseX = e.clientX; startMouseY = e.clientY; header.setPointerCapture(e.pointerId); floatWin.style.userSelect = 'none'; e.preventDefault(); }); header.addEventListener("pointermove", e => { if (!isDown) return; const rawX = dragCurX + (e.clientX - startMouseX); const rawY = dragCurY + (e.clientY - startMouseY); // 边界限制:只保证标题栏不超出浏览器,内容区允许超出 const headerW = header.offsetWidth || 200; const headerH = header.offsetHeight || 40; const vp = xxtFloatViewport(); const maxX = vp.w - headerW; const maxY = vp.h - headerH; const x = Math.max(0, Math.min(maxX, rawX)); const y = Math.max(0, Math.min(maxY, rawY)); floatWin.style.transform = `translate(${x}px, ${y}px)`; }, { passive: true }); header.addEventListener("pointerup", e => { if (!isDown) return; isDown = false; const rawX = dragCurX + (e.clientX - startMouseX); const rawY = dragCurY + (e.clientY - startMouseY); const headerW = header.offsetWidth || 200; const headerH = header.offsetHeight || 40; const vp = xxtFloatViewport(); const maxX = vp.w - headerW; const maxY = vp.h - headerH; dragCurX = Math.max(0, Math.min(maxX, rawX)); dragCurY = Math.max(0, Math.min(maxY, rawY)); header.releasePointerCapture(e.pointerId); floatWin.style.userSelect = ''; if (!isMax) saveFloatPos(dragCurX, dragCurY); }); header.addEventListener("selectstart", e => { e.preventDefault(); }); } // 初始化拖拽功能 setTimeout(setupDragging, 100); // 按钮逻辑 - 立即设置,不等待其他条件 // 悬浮窗初始化函数 // 悬浮窗初始化函数 function initFloatingWindow() { const minBtn = top.document.getElementById("xxt-min"); const closeBtn = top.document.getElementById("xxt-close"); if (minBtn) { minBtn.onclick = () => { if (!isMinimized) { // 最小化:只显示标题栏,按钮变为恢复图标 top.document.getElementById("xxt-helper-content").style.display = "none"; top.document.getElementById("xxt-helper-window").style.height = "auto"; top.document.getElementById("xxt-helper-window").style.minHeight = "auto"; // 显示标题栏进度 const minProgress = top.document.getElementById("xxt-min-progress"); if (minProgress) minProgress.style.display = "inline"; isMinimized = true; try { top.__xxtFloatMinimized = true; } catch (e) { } minBtn.textContent = '▢'; minBtn.title = '恢复'; } else { // 恢复:显示完整内容,按钮变回最小化图标 top.document.getElementById("xxt-helper-content").style.display = "flex"; top.document.getElementById("xxt-helper-window").style.height = "560px"; top.document.getElementById("xxt-helper-window").style.minHeight = "160px"; // 隐藏标题栏进度 const minProgress = top.document.getElementById("xxt-min-progress"); if (minProgress) minProgress.style.display = "none"; isMinimized = false; try { top.__xxtFloatMinimized = false; } catch (e) { } minBtn.textContent = '─'; minBtn.title = '最小化'; } }; } if (closeBtn) { closeBtn.onclick = () => { floatWin.style.display = "none"; }; } // 同页切章后若曾最小化,直接恢复折叠(不模拟 click,避免误切换) if (isMinimized && minBtn) { try { top.document.getElementById("xxt-helper-content").style.display = "none"; top.document.getElementById("xxt-helper-window").style.height = "auto"; top.document.getElementById("xxt-helper-window").style.minHeight = "auto"; const minProgress = top.document.getElementById("xxt-min-progress"); if (minProgress) minProgress.style.display = "inline"; minBtn.textContent = '▢'; minBtn.title = '恢复'; } catch (e) { console.log('[悬浮窗] 恢复最小化状态失败:', e); } } xxtSyncFloatWindowVersion(); xxtSyncDonateImg(donateSrc); } // 设置按钮事件处理 setTimeout(initFloatingWindow, 200); } try { top.xxtMountHelperFloatWindow = xxtMountHelperFloatWindow; } catch (e) {} if (_l.href.indexOf("knowledge/cards") > 0) { let spans = _d.getElementsByTagName('span'); for (let i = 0, l = spans.length; i < l; i++) { if (spans[i].innerHTML.indexOf('章节未开放') != -1) { if (_l.href.indexOf("ut=s") != -1) { _l.href = _l.href.replace("ut=s", "ut=t").replace(/&cpi=[0-9]{1,10}/, ''); } else if (_l.href.indexOf("ut=t") != -1) { spans[i].innerHTML = '此课程为闯关模式,请回到上一章节完成学习任务!' return; } break; } } _w.top.unrivalPageRd = String(Math.random()); try { _w.top.xxtNoTaskJumpAttempted = false; } catch (_) { } if (!isFf) { try { cVersion = parseInt(navigator.userAgent.match(/Chrome\/[0-9]{2,3}./)[0].replace('Chrome/', '') .replace('.', '')); } catch (e) { } } var busyThread = 0, getStr = (str, start, end) => { let res = str.substring(str.indexOf(start), str.indexOf(end)).replace(start, ''); return res; }, scripts = _d.getElementsByTagName('script'), param = null; for (let i = 0, l = scripts.length; i < l; i++) { if (scripts[i].innerHTML.indexOf('mArg = "";') != -1 && scripts[i].innerHTML.indexOf( '==UserScript==') == -1) { param = getStr(scripts[i].innerHTML, 'try{\n mArg = ', ';\n}catch(e){'); } } if (param == null) { xxtMountHelperFloatWindow(); return; } try { vrefer = _d.getElementsByClassName('ans-attach-online ans-insertvideo-online')[0].src; } catch (e) { vrefer = _p + '//' + _h + '/ananas/modules/video/index.html?v=2022-1118-1729'; } GM_setValue('vrefer', vrefer); GM_setValue('host', _h); xxtMountHelperFloatWindow(); var logs = { "logArry": [], "addLog": function (str, color = "black") { if (this.logArry.length >= 50) { this.logArry.splice(0, 1); } var nowTime = new Date(); var nowHour = (Array(2).join(0) + nowTime.getHours()).slice(-2); var nowMin = (Array(2).join(0) + nowTime.getMinutes()).slice(-2); var nowSec = (Array(2).join(0) + nowTime.getSeconds()).slice(-2); this.logArry.push("[" + nowHour + ":" + nowMin + ":" + nowSec + "] " + str + ""); let logStr = ""; for (let logI = 0, logLen = this.logArry.length; logI < logLen; logI++) { logStr += this.logArry[logI] + "
"; } try { top.document.getElementById('log').innerHTML = logStr; var logElement = top.document.getElementById('log'); logElement.scrollTop = logElement.scrollHeight; } catch (e) { console.error("日志更新失败:", e); } }, "getLogs": function () { return this.logArry.map(log => { // 移除HTML标签和时间戳,只返回纯文本内容 return log.replace(/<[^>]*>/g, '').replace(/\[\d{2}:\d{2}:\d{2}\]\s*/, ''); }); } }, xxtRequestJump = (reason, logMsg) => { if (jumpType == 2) { return false; } if (!xxtHasNextChapter()) { stop = true; xxtNotifyChapterEnd(); return false; } try { _w.top.jump = true; } catch (_) { } logs.addLog(logMsg || '准备跳转下一节…', logMsg && logMsg.indexOf('🎉') >= 0 ? 'green' : 'blue'); console.log('[章节跳转] jump reason=' + reason); return true; }, xxtTryNoTaskJump = (logMsg) => { try { if (_w.top.xxtNoTaskJumpAttempted) { return false; } _w.top.xxtNoTaskJumpAttempted = true; } catch (_) { } return xxtRequestJump('noTasks', logMsg); }, // 任务切换前清理 cards 侧定时器与测验 iframe(阅读助手模块不在此清理) cleanupBeforeTask = (reason) => { let cleared = []; if (simVideoWorker) { try { simVideoWorker.aborted = true; try { simVideoWorker.postMessage({ type: 'stop' }); } catch (_) { } simVideoWorker.terminate(); } catch (_) { } simVideoWorker = null; cleared.push('simVideoWorker'); } if (realPlayWatch || realVideoProgressMonitor) { xxtStopRealPlayWatch(); cleared.push('realVideo'); } if (workInfoLoop || workCheckcross || workDoneInterval) { cleared.push('workTimers'); } try { const wp = top.document.getElementById('workPanel'); if (wp && wp.style.display !== 'none') cleared.push('workPanel'); } catch (_) { } xxtResetWorkPanel(); if (cleared.length > 0) { logs.addLog('🧹 任务切换:已清理脚本定时器与测验 iframe', 'blue'); console.log('[资源清理]' + (reason ? ' ' + reason : '') + ' cleared:', cleared.join(',')); } }, clampPercent = (value) => { if (typeof value !== 'number' || isNaN(value)) return 0; return Math.max(0, Math.min(100, Math.round(value))); }, progressTracker = { state: { taskName: '暂无任务', percent: 0, type: '-', detail: '等待任务开始' }, elements: { name: null, percent: null, bar: null, detail: null, type: null }, init: function () { if (!this.elements.name) { this.elements = { name: top.document.getElementById('progressTaskName'), percent: top.document.getElementById('progressTaskPercent'), bar: top.document.getElementById('progressBarFill'), detail: top.document.getElementById('progressTaskDetail'), type: top.document.getElementById('progressTaskType') }; } return this.elements.name && this.elements.percent && this.elements.bar && this.elements.detail && this.elements.type; }, render: function () { if (!this.init()) return; // 使用 requestAnimationFrame 优化渲染 if (!this.updateScheduled) { this.updateScheduled = true; requestAnimationFrame(() => { this.updateScheduled = false; this.elements.name.textContent = this.state.taskName; this.elements.percent.textContent = this.state.percent + '%'; this.elements.bar.style.width = this.state.percent + '%'; this.elements.bar.setAttribute('aria-valuenow', this.state.percent); this.elements.detail.textContent = this.state.detail; this.elements.type.textContent = '类型:' + this.state.type; // 更新标题栏简化进度显示 const minProgress = top.document.getElementById('xxt-min-progress'); if (minProgress) { if (this.state.taskName && this.state.taskName !== '暂无任务') { minProgress.textContent = this.state.taskName + ' ' + this.state.percent + '%'; } else { minProgress.textContent = '暂无任务'; } } }); } }, update: function (patch = {}) { this.state = Object.assign({}, this.state, patch); if (typeof this.state.percent !== 'number') { this.state.percent = 0; } this.state.percent = clampPercent(this.state.percent); this.render(); }, reset: function (message = '等待任务开始') { this.update({ taskName: '暂无任务', percent: 0, type: '-', detail: message }); } }, getTaskName = (item) => { if (!item) return '未命名任务'; if (item['name']) return item['name']; if (item['property']) { if (item['property']['name']) return item['property']['name']; if (item['property']['title']) return item['property']['title']; } return '未命名任务'; }, buildTaskTitle = (typeLabel, item) => { return '[' + typeLabel + '] ' + getTaskName(item); }, reportProgress = (typeLabel, item, percent, detail) => { progressTracker.update({ taskName: buildTaskTitle(typeLabel, item), type: typeLabel, percent: percent, detail: detail }); }, formatDuration = (seconds = 0) => { const total = Math.max(0, Math.floor(Number(seconds) || 0)); const h = Math.floor(total / 3600); const m = Math.floor((total % 3600) / 60); const s = total % 60; const pad = (n) => String(n).padStart(2, '0'); if (h > 0) { return `${pad(h)}:${pad(m)}:${pad(s)}`; } return `${pad(m)}:${pad(s)}`; }, htmlHook = setInterval(function () { if (top.document.getElementById('unrivalRate') && top.document.getElementById('updateRateButton') && top.document .getElementById('reviewModeButton') && top.document.getElementById('autoDoWorkButton') && top.document .getElementById('autoSubmitButton') && top.document.getElementById('autoSaveButton') && top.document .getElementById('randomDoButton')) { clearInterval(htmlHook); progressTracker.render(); if (cVersion < 86) { logs.addLog( '\u60a8\u7684\u6d4f\u89c8\u5668\u5185\u6838\u8fc7\u8001\uff0c\u8bf7\u66f4\u65b0\u7248\u672c\u6216\u4f7f\u7528\u4e3b\u6d41\u6d4f\u89c8\u5668\uff0c\u63a8\u8350\u003c\u0061\u0020\u0068\u0072\u0065\u0066\u003d\u0022\u0068\u0074\u0074\u0070\u0073\u003a\u002f\u002f\u0077\u0077\u0077\u002e\u006d\u0069\u0063\u0072\u006f\u0073\u006f\u0066\u0074\u002e\u0063\u006f\u006d\u002f\u007a\u0068\u002d\u0063\u006e\u002f\u0065\u0064\u0067\u0065\u0022\u0020\u0074\u0061\u0072\u0067\u0065\u0074\u003d\u0022\u0076\u0069\u0065\u0077\u005f\u0077\u0069\u006e\u0064\u006f\u0077\u0022\u003e\u0065\u0064\u0067\u0065\u6d4f\u89c8\u5668\u003c\u002f\u0061\u003e', 'red'); stop = true; return; } if (isMobile) { logs.addLog('手机浏览器不保证能正常运行此脚本', 'orange'); } _d.addEventListener('visibilitychange', function () { let isH = _d.hidden; if (!isH) { logs.addLog('挂机功能不稳定,不建议长时间最小化窗口', 'orange'); } }); top.document.getElementById('unrivalRate').value = rateSimulate; const rateSelectInit = top.document.getElementById('unrivalRateSelect'); if (rateSelectInit) rateSelectInit.value = String(rateReal); // 添加上下箭头按钮 - 美化版原生spinner样式 const rateInput = top.document.getElementById('unrivalRate'); if (rateInput && !rateInput.parentNode.querySelector('.rate-controls')) { const controls = top.document.createElement('div'); controls.className = 'rate-controls'; controls.style.cssText = ` display: inline-block; vertical-align: top; margin-left: -1px; width: 18px; height: 44px; border: 1px solid #ccc; border-left: none; background: #fff; position: relative; border-radius: 0 4px 4px 0; box-sizing: border-box; `; const upBtn = top.document.createElement('div'); upBtn.innerHTML = '▲'; upBtn.style.cssText = ` position: absolute; top: 0; left: 0; right: 0; height: 22px; border-bottom: 1px solid #e0e0e0; background: linear-gradient(to bottom, #fafafa, #f0f0f0); cursor: pointer; font-size: 7px; line-height: 22px; text-align: center; color: #555; user-select: none; transition: all 0.1s ease; `; upBtn.onclick = () => { const current = parseFloat(rateInput.value) || 1; rateInput.value = current + 1; }; const downBtn = top.document.createElement('div'); downBtn.innerHTML = '▼'; downBtn.style.cssText = ` position: absolute; bottom: 0; left: 0; right: 0; height: 22px; background: linear-gradient(to bottom, #f0f0f0, #fafafa); cursor: pointer; font-size: 7px; line-height: 22px; text-align: center; color: #555; user-select: none; transition: all 0.1s ease; `; downBtn.onclick = () => { const current = parseFloat(rateInput.value) || 1; rateInput.value = Math.max(current - 1, 1); }; // 添加悬停和点击效果 upBtn.onmouseover = () => { upBtn.style.background = 'linear-gradient(to bottom, #e8f4fd, #d1ecf1)'; upBtn.style.color = '#007bff'; }; upBtn.onmouseout = () => { upBtn.style.background = 'linear-gradient(to bottom, #fafafa, #f0f0f0)'; upBtn.style.color = '#555'; }; upBtn.onmousedown = () => { upBtn.style.background = 'linear-gradient(to bottom, #d1ecf1, #bee5eb)'; }; upBtn.onmouseup = () => { upBtn.style.background = 'linear-gradient(to bottom, #e8f4fd, #d1ecf1)'; }; downBtn.onmouseover = () => { downBtn.style.background = 'linear-gradient(to bottom, #d1ecf1, #e8f4fd)'; downBtn.style.color = '#007bff'; }; downBtn.onmouseout = () => { downBtn.style.background = 'linear-gradient(to bottom, #f0f0f0, #fafafa)'; downBtn.style.color = '#555'; }; downBtn.onmousedown = () => { downBtn.style.background = 'linear-gradient(to bottom, #bee5eb, #d1ecf1)'; }; downBtn.onmouseup = () => { downBtn.style.background = 'linear-gradient(to bottom, #d1ecf1, #e8f4fd)'; }; controls.appendChild(upBtn); controls.appendChild(downBtn); rateInput.parentNode.insertBefore(controls, rateInput.nextSibling); } // rate-controls 创建后同步播放模式 UI(真实模式隐藏数字输入框) try { if (typeof top.__xxtApplyVideoPlayModeUI === 'function') { top.__xxtApplyVideoPlayModeUI(videoPlayMode); } } catch (e) { } top.document.getElementById('updateToken').onclick = function () { var token = top.document.getElementById('token').value; logs.addLog('题库token已更新为' +token, 'green'); GM_setValue('tikutoken', token); } top.document.getElementById('accuracy').value=accuracy; top.document.getElementById('updateaccuracy').onclick = function () { var uaccuracy = top.document.getElementById('accuracy').value; if (parseFloat(uaccuracy) == parseInt(uaccuracy)) { uaccuracy = parseInt(uaccuracy); } else { uaccuracy = parseFloat(uaccuracy); } GM_setValue('accuracy', uaccuracy); accuracy = uaccuracy; if (uaccuracy >= 0 && uaccuracy<=100) { logs.addLog('章节测试正确率已更新为'+uaccuracy+'%,将在3秒内生效', 'green'); } else { logs.addLog('奇怪正确率', 'orange'); } } top.document.getElementById('updateRateButton').onclick = function () { let urate; if (videoPlayMode === 'real') { const sel = top.document.getElementById('unrivalRateSelect'); urate = normalizeRealVideoRate(sel ? sel.value : rateReal); rateReal = urate; GM_setValue('unrivalrate_real', urate); if (sel) sel.value = String(urate); if (urate > 0) { logs.addLog('真实播放倍速已保存为 ' + urate + ' 倍,下一节视频或刷新页面后生效', 'green'); if (urate > 2) { logs.addLog('⚠️ 倍速较高,存在被检测风险,请谨慎使用', 'orange'); } } else { logs.addLog('奇怪的真实播放倍速,请重新选择', 'orange'); } } else { urate = top.document.getElementById('unrivalRate').value; if (parseFloat(urate) == parseInt(urate, 10)) { urate = parseInt(urate, 10); } else { urate = parseFloat(urate); } if (!isFinite(urate)) urate = 1; rateSimulate = urate; GM_setValue('unrivalrate_simulate', urate); top.document.getElementById('unrivalRate').value = urate; if (urate > 0) { logs.addLog('模拟倍速已保存为 ' + urate + ' 倍,将在下一节模拟视频生效', 'green'); if (urate > 3) { logs.addLog('⚠️ 倍速较高,存在被检测风险,请谨慎使用', 'orange'); } } else { logs.addLog('奇怪的倍速,将会自动跳过视频任务', 'orange'); } } } top.document.getElementById('reportInterval').value = reportInterval; top.document.getElementById('updateReportInterval').onclick = function () { let intervalValue = parseInt(top.document.getElementById('reportInterval').value, 10); if (isNaN(intervalValue) || intervalValue < 10) { intervalValue = 90; logs.addLog('上报间隔设置无效,已恢复为默认 90 秒', 'orange'); } reportInterval = intervalValue; GM_setValue('reportInterval', reportInterval); top.document.getElementById('reportInterval').value = reportInterval; logs.addLog('视频上报间隔已更新为 ' + reportInterval + ' 秒,将在下次上报时生效', 'green'); } top.document.getElementById('backGround').onclick = function () { let backGroundButton = top.document.getElementById('backGround'); if (backGroundButton.getAttribute('class') == 'btn btn-default') { backGroundButton.setAttribute('class', 'btn btn-success'); logs.addLog('挂机已激活(最小化标签页时静音续播)', 'green'); _w.top.backNow = 1; GM_setValue('unrivalbackground', '1'); // 保存激活状态 } else { backGroundButton.setAttribute('class', 'btn btn-default'); logs.addLog('挂机已关闭', 'green'); _w.top.backNow = 0; GM_setValue('unrivalbackground', '0'); // 保存取消激活状态 } } top.document.getElementById('reviewModeButton').onclick = function () { let reviewButton = top.document.getElementById('reviewModeButton'); if (reviewButton.getAttribute('class') == 'btn btn-default') { // 开启复习模式:切换为 btn-success(绿色样式) reviewButton.setAttribute('class', 'btn btn-success'); logs.addLog('复习模式已开启...', 'green'); // 日志文字也同步为绿色,呼应按钮颜色 GM_setValue('unrivalreview', '1'); _w.top.unrivalReviewMode = '1'; } else { // 关闭复习模式:恢复为 btn-default(默认白色样式) reviewButton.setAttribute('class', 'btn btn-default'); logs.addLog('复习模式已关闭...', 'green'); GM_setValue('unrivalreview', '0'); _w.top.unrivalReviewMode = '0'; } } top.document.getElementById('autoDoWorkButton').onclick = function () { let autoDoWorkButton = top.document.getElementById('autoDoWorkButton'); if (autoDoWorkButton.getAttribute('class') == 'btn btn-default') { // 开启自动答题:切换为 btn-success(绿色样式) autoDoWorkButton.setAttribute('class', 'btn btn-success'); logs.addLog('自动答题已开启...', 'green'); // 日志文字也同步为绿色,呼应按钮颜色 GM_setValue('unrivaldowork', '1'); _w.top.unrivalDoWork = '1'; } else { // 关闭自动答题:恢复为 btn-default(默认白色样式) autoDoWorkButton.setAttribute('class', 'btn btn-default'); logs.addLog('自动答题已关闭...', 'green'); GM_setValue('unrivaldowork', '0'); _w.top.unrivalDoWork = '0'; } } top.document.getElementById('autoSubmitButton').onclick = function () { let autoSubmitButton = top.document.getElementById('autoSubmitButton'); if (autoSubmitButton.getAttribute('class') == 'btn btn-default') { // 开启自动提交:切换为 btn-success(绿色样式) autoSubmitButton.setAttribute('class', 'btn btn-success'); logs.addLog('自动提交已开启...', 'green'); // 日志文字也同步为绿色,呼应按钮颜色 GM_setValue('unrivalautosubmit', '1'); _w.top.unrivalAutoSubmit = '1'; } else { // 关闭自动提交:恢复为 btn-default(默认白色样式) autoSubmitButton.setAttribute('class', 'btn btn-default'); logs.addLog('自动提交已关闭...', 'green'); GM_setValue('unrivalautosubmit', '0'); _w.top.unrivalAutoSubmit = '0'; } } top.document.getElementById('autoSaveButton').onclick = function () { let autoSaveButton = top.document.getElementById('autoSaveButton'); if (autoSaveButton.getAttribute('class') == 'btn btn-default') { // 开启自动保存:切换为 btn-success(绿色样式) autoSaveButton.setAttribute('class', 'btn btn-success'); logs.addLog('自动保存已开启...', 'green'); // 日志文字也同步为绿色,呼应按钮颜色 GM_setValue('unrivalautosave', '1'); _w.top.unrivalAutoSave = '1'; } else { // 关闭自动保存:恢复为 btn-default(默认白色样式) autoSaveButton.setAttribute('class', 'btn btn-default'); logs.addLog('自动保存已关闭...', 'green'); GM_setValue('unrivalautosave', '0'); _w.top.unrivalAutoSave = '0'; } } top.document.getElementById('randomDoButton').onclick = function () { let randomDoButton = top.document.getElementById('randomDoButton'); if (randomDoButton.getAttribute('class') == 'btn btn-default') { // 开启随机答题:切换为 btn-success(绿色样式) randomDoButton.setAttribute('class', 'btn btn-success'); randomDoButton.innerHTML = '随机答题: 开启'; logs.addLog('随机答题已开启,找不到答案的单选、多选、判断会自动选【C、ABCD、错】', 'green'); GM_setValue('unrivalrandomdo', 1); _w.top.unrivalRandomDo = 1; randomDo = 1; } else { // 关闭随机答题:恢复为 btn-default(默认白色样式) randomDoButton.setAttribute('class', 'btn btn-default'); randomDoButton.innerHTML = '随机答题: 关闭'; logs.addLog('随机答题已关闭...', 'green'); GM_setValue('unrivalrandomdo', 0); _w.top.unrivalRandomDo = 0; randomDo = 0; } } top.document.getElementById('videoTimeButton').onclick = function () { top.document.getElementById('videoTime').style.display = 'block'; top.document.getElementById('videoTimeContent').src = _p + '//stat2-ans.chaoxing.com/task/s/index?courseid=' + courseId + '&clazzid=' + classId; } // 初始设置按钮状态和颜色 top.document.getElementById('jumpTypeButton').textContent = '模式: ' + (jumpType === 2 ? '不跳转' : '遍历'); top.document.getElementById('disableMonitorButton').textContent = '多端学习: ' + (disableMonitor === 1 ? '解除' : '启用'); top.document.getElementById('autoLoginButton').textContent = '自动登录: ' + (autoLogin === 1 ? '开启' : '关闭'); top.document.getElementById('videoQuestionButton').textContent = '视频题目: ' + (videoQuestionEnabled ? '开启' : '关闭'); top.document.getElementById('autoStartButton').textContent = '自动阅读: ' + (GM_getValue('autoStart', true) ? '开启' : '关闭'); top.document.getElementById('phoneNumber').value = GM_getValue('phoneNumber', ''); top.document.getElementById('password').value = GM_getValue('password', ''); // 设置初始按钮颜色 if (disableMonitor === 1) { top.document.getElementById('disableMonitorButton').setAttribute('class', 'btn btn-success'); } else { top.document.getElementById('disableMonitorButton').setAttribute('class', 'btn btn-default'); } if (autoLogin === 1) { top.document.getElementById('autoLoginButton').setAttribute('class', 'btn btn-success'); } else { top.document.getElementById('autoLoginButton').setAttribute('class', 'btn btn-default'); } if (videoQuestionEnabled) { top.document.getElementById('videoQuestionButton').setAttribute('class', 'btn btn-success'); } else { top.document.getElementById('videoQuestionButton').setAttribute('class', 'btn btn-default'); } // 视频播放模式、自动静音与倍速控件初始化 (function initVideoPlayModeUI() { const radioReal = top.document.getElementById('videoModeReal'); const radioSim = top.document.getElementById('videoModeSimulate'); const muteCb = top.document.getElementById('autoMuteVideoCheckbox'); const muteLabel = top.document.getElementById('autoMuteVideoLabel'); const rateInput = top.document.getElementById('unrivalRate'); const rateSelect = top.document.getElementById('unrivalRateSelect'); // 1) radio 初始选中 if (videoPlayMode === 'real') { if (radioReal) radioReal.checked = true; } else { if (radioSim) radioSim.checked = true; } // 2) checkbox 初始选中 if (muteCb) muteCb.checked = !!autoMuteVideo; // 3) 双倍速控件:根据模式切显示 + 自动静音是否生效的视觉提示 const applyModeUI = (mode) => { if (rateInput && rateSelect) { if (mode === 'real') { rateInput.style.display = 'none'; rateSelect.style.display = 'inline-block'; // input 自带的上下箭头容器也一起隐藏 const ctrls = rateInput.parentNode && rateInput.parentNode.querySelector('.rate-controls'); if (ctrls) ctrls.style.display = 'none'; } else { rateInput.style.display = 'inline-block'; rateSelect.style.display = 'none'; const ctrls = rateInput.parentNode && rateInput.parentNode.querySelector('.rate-controls'); if (ctrls) ctrls.style.display = 'inline-block'; } } if (muteLabel) muteLabel.style.opacity = (mode === 'real') ? '1' : '0.5'; }; applyModeUI(videoPlayMode); // 4) select 同步真实播放倍速(0.75~3,步进 0.25) if (rateSelect) { rateSelect.value = String(rateReal); } if (rateInput) { rateInput.value = rateSimulate; } // 暴露给后续事件用 top.__xxtApplyVideoPlayModeUI = applyModeUI; })(); // 自动阅读按钮颜色初始化 if (GM_getValue('autoStart', true)) { top.document.getElementById('autoStartButton').setAttribute('class', 'btn btn-success'); } else { top.document.getElementById('autoStartButton').setAttribute('class', 'btn btn-default'); } // jumpType切换事件(遍历 ↔ 不跳转) top.document.getElementById('jumpTypeButton').onclick = function () { jumpType = jumpType === 1 ? 2 : 1; this.textContent = '模式: ' + (jumpType === 2 ? '不跳转' : '遍历'); GM_setValue('jumpType', jumpType); logs.addLog('已切换到' + (jumpType === 2 ? '不跳转' : '遍历') + '模式', 'green'); }; // disableMonitor切换事件 top.document.getElementById('disableMonitorButton').onclick = function () { disableMonitor = (disableMonitor === 1) ? 0 : 1; let statusText = (disableMonitor === 1) ? '解除' : '启用'; this.textContent = '多端学习: ' + statusText; GM_setValue('disableMonitor', disableMonitor); if (disableMonitor === 1) { this.setAttribute('class', 'btn btn-success'); logs.addLog('多端学习已开启', 'green'); // 立即劫持appendChild,防止监控脚本注入 _w.appendChild = _w.Element.prototype.appendChild; _w.Element.prototype.appendChild = function () { try { if (arguments[0].src && arguments[0].src.indexOf('detect.chaoxing.com') > 0) { return; } } catch (e) { } _w.appendChild.apply(this, arguments); }; } else { this.setAttribute('class', 'btn btn-default'); logs.addLog('多端学习已关闭', 'green'); // 立即恢复原生appendChild if (_w.appendChild) { _w.Element.prototype.appendChild = _w.appendChild; } } }; // autoLogin切换事件 top.document.getElementById('autoLoginButton').onclick = function () { autoLogin = (autoLogin === 1) ? 0 : 1; this.textContent = '自动登录: ' + (autoLogin === 1 ? '开启' : '关闭'); GM_setValue('autoLogin', autoLogin); if (autoLogin === 1) { this.setAttribute('class', 'btn btn-success'); logs.addLog('自动登录功能已开启', 'green'); } else { this.setAttribute('class', 'btn btn-default'); logs.addLog('自动登录功能已关闭', 'green'); } }; // videoQuestion切换事件 top.document.getElementById('videoQuestionButton').onclick = function () { videoQuestionEnabled = !videoQuestionEnabled; this.textContent = '视频题目: ' + (videoQuestionEnabled ? '开启' : '关闭'); GM_setValue('videoQuestionEnabled', videoQuestionEnabled); if (videoQuestionEnabled) { this.setAttribute('class', 'btn btn-success'); logs.addLog('视频题目自动处理功能已开启', 'green'); } else { this.setAttribute('class', 'btn btn-default'); logs.addLog('视频题目自动处理功能已关闭', 'green'); } }; // 视频播放模式切换事件 (function bindVideoPlayModeEvents() { const radios = top.document.querySelectorAll('input[name="videoPlayMode"]'); const rateInput = top.document.getElementById('unrivalRate'); const rateSelect = top.document.getElementById('unrivalRateSelect'); const muteCb = top.document.getElementById('autoMuteVideoCheckbox'); const onModeChange = (newMode) => { videoPlayMode = newMode; GM_setValue('videoPlayMode', videoPlayMode); if (typeof top.__xxtApplyVideoPlayModeUI === 'function') { top.__xxtApplyVideoPlayModeUI(newMode); } if (newMode === 'real') { if (rateSelect) rateSelect.value = String(rateReal); logs.addLog('已切换为真实播放模式(实际播放视频,由学习通自身上报进度),倍速:' + rateReal + 'x', 'green'); } else { if (rateInput) rateInput.value = rateSimulate; logs.addLog('已切换为模拟模式(不实际播放,由脚本上报进度),倍速:' + rateSimulate + 'x', 'blue'); } }; radios.forEach(r => { r.addEventListener('change', function () { if (this.checked) onModeChange(this.value); }); }); if (muteCb) { muteCb.addEventListener('change', function () { autoMuteVideo = !!this.checked; GM_setValue('autoMuteVideo', autoMuteVideo); logs.addLog(autoMuteVideo ? '真实播放模式下将自动静音(含音频/mp3)' : '真实播放模式下不再自动静音', autoMuteVideo ? 'green' : 'blue'); }); } })(); // 自动阅读按钮切换事件 top.document.getElementById('autoStartButton').onclick = function () { const currentAutoStart = GM_getValue('autoStart', true); const newAutoStart = !currentAutoStart; GM_setValue('autoStart', newAutoStart); this.textContent = '自动阅读: ' + (newAutoStart ? '开启' : '关闭'); if (newAutoStart) { this.setAttribute('class', 'btn btn-success'); logs.addLog('自动阅读功能已开启', 'green'); } else { this.setAttribute('class', 'btn btn-default'); logs.addLog('自动阅读功能已关闭', 'green'); } }; // 保存登录信息事件 top.document.getElementById('saveLoginInfo').onclick = function () { let newPhone = top.document.getElementById('phoneNumber').value; let newPassword = top.document.getElementById('password').value; if (newPhone && !/^1[3-9]\d{9}$/.test(newPhone)) { logs.addLog('手机号格式错误,请重新输入', 'red'); return; } GM_setValue('phoneNumber', newPhone); GM_setValue('password', newPassword); phoneNumber = newPhone; password = newPassword; logs.addLog('登录信息已保存', 'green'); }; } }, 100), loopjob = () => { if (_w.top.unrivalScriptList.length > 1) { logs.addLog('您同时开启了多个刷课脚本,建议关闭其他脚本,否则会有挂科风险!', 'red'); } if (cVersion < 8.6 * 10) { logs.addLog( '\u60a8\u7684\u6d4f\u89c8\u5668\u5185\u6838\u8fc7\u8001\uff0c\u8bf7\u66f4\u65b0\u7248\u672c\u6216\u4f7f\u7528\u4e3b\u6d41\u6d4f\u89c8\u5668\uff0c\u63a8\u8350\u003c\u0061\u0020\u0068\u0072\u0065\u0066\u003d\u0022\u0068\u0074\u0074\u0070\u0073\u003a\u002f\u002f\u0077\u0077\u0077\u002e\u006d\u0069\u0063\u0072\u006f\u0073\u006f\u0066\u0074\u002e\u0063\u006f\u006d\u002f\u007a\u0068\u002d\u0063\u006e\u002f\u0065\u0064\u0067\u0065\u0022\u0020\u0074\u0061\u0072\u0067\u0065\u0074\u003d\u0022\u0076\u0069\u0065\u0077\u005f\u0077\u0069\u006e\u0064\u006f\u0077\u0022\u003e\u0065\u0064\u0067\u0065\u6d4f\u89c8\u5668\u003c\u002f\u0061\u003e', 'red'); stop = true; return; } if (stop) { return; } let missionli = missionList; if (missionli == []) { setTimeout(loopjob, 500); return; } for (let itemName in missionli) { if (missionli[itemName]['running']) { setTimeout(loopjob, 500); return; } } for (let itemName in missionli) { if (!missionli[itemName]['done']) { cleanupBeforeTask('loopjob'); switch (missionli[itemName]['type']) { case 'video': doVideo(missionli[itemName]).catch(e => { logs.addLog('视频任务执行出错: ' + e.message, 'red'); }); break; case 'document': doDocument(missionli[itemName]); break; case 'work': doWork(missionli[itemName]); break; } setTimeout(loopjob, 500); return; } } if (busyThread <= 0) { if (jumpType != 2) { xxtRequestJump('allDone', '🎉 所有任务处理完毕,准备跳转下一节'); } else { logs.addLog('所有任务处理完毕,用户设置为不跳转,脚本已结束运行,如需自动跳转,请到任务配置界面改变模式为遍历', 'green'); } clearInterval(loopjob); } else { setTimeout(loopjob, 500); } }, readyCheck = () => { setTimeout(function () { try { if (!xxtHasNextChapter() && !xxtChapterEndLogged) { xxtChapterEndLogged = true; logs.addLog('已是最后一章,无下一节可跳转,本页任务完成后脚本将停止', 'green'); } if (!isCat) { logs.addLog( '推荐使用脚本猫运行此脚本,使用其他脚本管理器不保证能正常运行', 'orange'); } if (_w.top.unrivalReviewMode == '1') { logs.addLog('复习模式已开启,遇到已完成的视频任务不会跳过', 'green'); top.document.getElementById('reviewModeButton').setAttribute('class', 'btn btn-success'); } else { top.document.getElementById('reviewModeButton').setAttribute('class', 'btn btn-default'); } var backGroundStatus = GM_getValue('unrivalbackground', '0'); if (backGroundStatus === '1') { _w.top.backNow = 1; top.document.getElementById('backGround').setAttribute('class', 'btn btn-success'); // 设置按钮为绿色 } else { top.document.getElementById('backGround').setAttribute('class', 'btn btn-default'); // 设置按钮为白色 } if (_w.top.unrivalDoWork == '1') { logs.addLog('自动做章节测试已开启,将会自动做章节测试', 'green'); top.document.getElementById('autoDoWorkButton').setAttribute('class', 'btn btn-success'); } else { top.document.getElementById('autoDoWorkButton').setAttribute('class', 'btn btn-default'); } if (_w.top.unrivalAutoSubmit == '1') { top.document.getElementById('autoSubmitButton').setAttribute('class', 'btn btn-success'); } else { top.document.getElementById('autoSubmitButton').setAttribute('class', 'btn btn-default'); } if (_w.top.unrivalAutoSave == '1') { top.document.getElementById('autoSaveButton').setAttribute('class', 'btn btn-success'); } else { top.document.getElementById('autoSaveButton').setAttribute('class', 'btn btn-default'); } if (_w.top.unrivalRandomDo == 1) { top.document.getElementById('randomDoButton').setAttribute('class', 'btn btn-success'); top.document.getElementById('randomDoButton').innerHTML = '随机答题: 开启'; } else { top.document.getElementById('randomDoButton').setAttribute('class', 'btn btn-default'); top.document.getElementById('randomDoButton').innerHTML = '随机答题: 关闭'; } } catch (e) { console.log(e); readyCheck(); return; } }, 500); } readyCheck(); try { var pageData = JSON.parse(param); } catch (e) { if (jumpType != 2) { xxtTryNoTaskJump('ℹ️ 此页无任务,准备跳转下一节'); } else { logs.addLog('此页无任务,用户设置为不跳转,脚本已结束运行,如需自动跳转,请到任务配置界面改变模式为遍历', 'green'); } return; } let data = pageData['defaults'], jobList = [], classId = data['clazzId'], chapterId = data['knowledgeid'], reportUrl = data['reportUrl'], ktoken = data['ktoken']; UID = UID || data['userid']; FID = FID || data['fid']; for (let i = 0, l = pageData['attachments'].length; i < l; i++) { let item = pageData['attachments'][i]; if (item['job'] != true || item['isPassed'] == true) { if (_w.top.unrivalReviewMode == '1' && item['type'] == 'video') { jobList.push(item); } continue; } else { jobList.push(item); } } // 从 mArg 读取已学进度(毫秒转秒) getTaskPlayedTimeSec = (attachmentItem) => { if (attachmentItem['headOffset'] > 0) { return Math.floor(attachmentItem['headOffset'] / 1000); } if (attachmentItem['playTime'] > 0) { return Math.floor(attachmentItem['playTime'] / 1000); } return 0; }, // 从页面 video 元素读取当前播放位置(用于续播) getIframePlayedTimeSec = (objectId) => { if (!objectId) { return 0; } const mediaIframes = _d.querySelectorAll( 'iframe.ans-insertvideo-online, iframe[src*="/ananas/modules/video/"], iframe[src*="/ananas/modules/audio/"]' ); for (let i = 0, l = mediaIframes.length; i < l; i++) { try { const dataAttr = mediaIframes[i].getAttribute('data'); if (!dataAttr) { continue; } const data = JSON.parse(dataAttr); if (!data || data.objectid !== objectId) { continue; } const iframeDoc = mediaIframes[i].contentDocument || (mediaIframes[i].contentWindow && mediaIframes[i].contentWindow.document); if (!iframeDoc) { continue; } const media = iframeDoc.querySelector('video') || iframeDoc.querySelector('audio'); if (media && media.currentTime > 0) { return Math.floor(media.currentTime); } } catch (e) { } } return 0; }, video_getReady = (item) => { const pageRd = _w.top.unrivalPageRd; let taskPlayedTimeSec = getTaskPlayedTimeSec(item), statusUrl = _p + '//' + _h + '/ananas/status/' + item['property']['objectid'] + '?k=' + FID + '&flag=normal&_dc=' + String(Math.round(new Date())), doubleSpeed = item['property']['doublespeed']; busyThread += 1; gmRequest({ method: "get", headers: { 'Host': _h, 'Referer': vrefer, 'Sec-Fetch-Site': 'same-origin' }, url: statusUrl, onload: function (res) { try { busyThread -= 1; if (pageRd != _w.top.unrivalPageRd) return; let videoInfo = JSON.parse(res.responseText), duration = videoInfo['duration'], dtoken = videoInfo['dtoken']; if (duration == undefined) { top.document.getElementById('joblist').innerHTML += `
` + '[无效视频]' + item['property']['name'] + `
` return; } missionList['m' + item['jobid']] = { 'module': item['property']['module'], 'type': 'video', 'dtoken': dtoken, 'duration': duration, 'objectId': item['property']['objectid'], 'property': item['property'], 'rt': item['property']['rt'] || '0.9', 'otherInfo': item['otherInfo'], 'doublespeed': doubleSpeed, 'jobid': item['jobid'], 'name': item['property']['name'], 'taskPlayedTimeSec': taskPlayedTimeSec, 'isPassed': item['isPassed'] == true, 'done': false, 'running': false }; top.document.getElementById('joblist').innerHTML += `
` + '[视频]' + item['property']['name'] + `
` } catch (e) { } }, onerror: function (err) { console.log(err); if (err.error.indexOf('@connect list') >= 0) { logs.addLog('请添加安全网址,将 【 //@connect ' + _h + ' 】方括号里的内容(不包括方括号)添加到脚本代码内指定位置,否则脚本无法正常运行,如图所示:', 'red'); logs.addLog( '' ); stop = true; } else { logs.addLog('获取任务详情失败', 'red'); logs.addLog('错误原因:' + err.error, 'red'); } } }); }, // 视频题目处理相关函数 parseVideoCpi = (item) => { const fromQuery = getQueryVariable('cpi'); if (fromQuery) return fromQuery; const other = (item && item['otherInfo']) || ''; const om = other.match(/cpi_(\d+)/); if (om) return om[1]; if (reportUrl) { const rm = String(reportUrl).match(/\/a\/(\d+)/); if (rm) return rm[1]; } return UID; }, requestVideoQuestionInit = (url) => { return new Promise((resolve, reject) => { gmRequest({ method: "get", url: url, headers: { 'Host': _h, 'Referer': vrefer, 'Sec-Fetch-Site': 'same-origin', 'User-Agent': navigator.userAgent, 'X-Requested-With': 'XMLHttpRequest' }, timeout: 10000, onload: function(res) { try { if (res.status === 200) { resolve(JSON.parse(res.responseText)); } else { reject(new Error('HTTP ' + res.status)); } } catch (e) { reject(e); } }, onerror: function(err) { reject(err); }, ontimeout: function() { reject(new Error('Request timeout')); } }); }); }, initVideoQuestions = async (mid, cpi, classid, courseid) => { const dc = new Date().valueOf(); const midEnc = encodeURIComponent(mid); const videoCourseId = courseid || (data && data['courseid']) || courseId || ''; const candidates = []; const seen = {}; const pushCandidate = (url) => { if (!url || seen[url]) return; seen[url] = true; candidates.push(url); }; const v2Query = 'mid=' + midEnc + '&cpi=' + cpi + '&classid=' + classid + (videoCourseId ? '&courseid=' + videoCourseId : '') + '&_dc=' + dc; pushCandidate(_p + '//' + _h + '/mooc-ans/richvideo/initdatawithviewerV2?' + v2Query); const initdataUrl = data && data['initdataUrl']; if (initdataUrl) { const v2FromInit = initdataUrl.replace(/initdatawithviewer$/i, 'initdatawithviewerV2'); if (v2FromInit !== initdataUrl) { pushCandidate(v2FromInit + '?' + v2Query); } pushCandidate(initdataUrl + '?start=undefined&mid=' + midEnc + '&_dc=' + dc); pushCandidate(initdataUrl + '?mid=' + midEnc + '&cpi=' + cpi + '&classid=' + classid + '&_dc=' + dc); } pushCandidate(_p + '//' + _h + '/richvideo/initdatawithviewer?start=undefined&mid=' + midEnc + '&_dc=' + dc); pushCandidate(_p + '//' + _h + '/ananas/initdatawithviewer?mid=' + midEnc + '&cpi=' + cpi + '&classid=' + classid + '&_dc=' + dc); let lastErr = null; let lastEmpty = null; for (let i = 0; i < candidates.length; i++) { try { const result = await requestVideoQuestionInit(candidates[i]); const count = Array.isArray(result) ? result.length : 0; if (count > 0) { return result; } lastEmpty = result; } catch (e) { lastErr = e; } } if (lastEmpty !== null) { return lastEmpty; } throw lastErr || new Error('initVideoQuestions failed'); }, flattenVideoQuestionItems = (res) => { const out = []; const seen = new WeakSet(); const walk = (node, depth) => { if (!node || depth > 6) return; if (Array.isArray(node)) { node.forEach(n => walk(n, depth + 1)); return; } if (typeof node !== 'object') return; if (seen.has(node)) return; const options = node.options; const hasOptions = Array.isArray(options) && options.length > 0; const resourceId = node.resourceId || node.eventid || node.eventId; if (hasOptions && (resourceId || node.description || node.title)) { seen.add(node); out.push(node); return; } if (Array.isArray(node.datas)) { node.datas.forEach(d => walk(d, depth + 1)); return; } if (node.datas && typeof node.datas === 'object') { walk(node.datas, depth + 1); return; } Object.keys(node).forEach(k => { if (k !== 'options') walk(node[k], depth + 1); }); }; walk(res, 0); return out; }, pickVideoQuestionAnswer = (item1) => { const options = item1.options; if (!options || !options.length) return ''; const right = options.filter(o => o.isRight === true || o.isRight === 1 || o.isRight === '1' || o.isRight === 'true'); if (right.length) { return right.map(o => (o.name || o.content || '').trim()).filter(Boolean).join(''); } const fromField = item1.rightAnswer || item1.answer || item1.correctAnswer || item1.rightOption || item1.standardAnswer; if (fromField) { return String(fromField).replace(/[^A-Ga-g]/g, '').toUpperCase(); } return ''; }, normalizeVideoQuizText = (text) => { return String(text || '').replace(/<[^>]+>/g, '').replace(/\s+/g, '') .replace(/[(())]/g, '').toLowerCase(); }, matchVideoQuestionFromCache = (cache, domText) => { if (!cache || !cache.length) return null; const normDom = normalizeVideoQuizText(domText); if (normDom) { for (let i = 0; i < cache.length; i++) { const normQ = normalizeVideoQuizText(cache[i].description); if (!normQ) continue; if (normDom.indexOf(normQ) >= 0 || normQ.indexOf(normDom) >= 0) { return cache[i]; } } } return cache.length === 1 ? cache[0] : null; }, clickVideoQuizOptionByAnswer = (optionLabels, answerLetter) => { if (!answerLetter || !optionLabels || !optionLabels.length) return false; const letter = String(answerLetter).trim().charAt(0).toUpperCase(); for (let i = 0; i < optionLabels.length; i++) { const label = optionLabels[i]; const text = (label.textContent || '').trim(); if (text.startsWith(letter) || text.startsWith(letter + '.') || text.startsWith(letter + '、') || text.startsWith(letter + '.')) { label.click(); return true; } const input = label.querySelector('input'); if (input && String(input.value || '').toUpperCase() === letter) { label.click(); return true; } } const idx = letter.charCodeAt(0) - 65; if (idx >= 0 && idx < optionLabels.length) { optionLabels[idx].click(); return true; } return false; }, buildVideoQuestionCache = async (item) => { const videoMid = (item['property'] && item['property']['mid']) || item['videoMid'] || ''; if (!videoMid) return []; const videoCpi = parseVideoCpi(item); const res = await initVideoQuestions(videoMid, videoCpi, classId, data && data['courseid']); if (!res || !res.length) return []; const questionList = flattenVideoQuestionItems(res); if (!questionList.length) return []; const cache = []; for (let i = 0; i < questionList.length; i++) { const q = questionList[i]; let answer = pickVideoQuestionAnswer(q); if (!answer) { const resourceId = q.resourceId || q.eventid || q.eventId; if (resourceId) { const submitResult = await trySubmitVideoQuestion(item, q, videoCpi, ''); if (submitResult) answer = submitResult.answer; } } if (!answer) continue; cache.push({ description: q.description || q.title || '', answer: answer }); } return cache; }, trySubmitVideoQuestion = async (item, item1, videoCpi, presetAnswer) => { const objectId = item['objectId']; const resourceId = item1.resourceId || item1.eventid || item1.eventId; const memberinfo = item1.memberinfo || item1.memberInfo || ''; const description = item1.description || item1.title || '视频内题目'; const answerList = []; const addAnswer = (v) => { const ans = (v || '').trim(); if (ans && !answerList.includes(ans)) answerList.push(ans); }; addAnswer(presetAnswer); (item1.options || []).forEach(o => addAnswer(o.name || o.content)); for (let i = 0; i < answerList.length; i++) { try { const res1 = await submitVideoAnswer(classId, videoCpi, objectId, resourceId, memberinfo, answerList[i]); if (res1 && res1.status) { return { answer: answerList[i], res1: res1, description: description }; } } catch (e) { } } return null; }, submitVideoAnswer = async (classid, cpi, objectid, eventid, memberinfo, answer) => { return new Promise((resolve, reject) => { let url = `${_p}//${_h}/question/quiz-validation?classid=${classid}&cpi=${cpi}&objectid=${objectid}&_dc=${new Date().valueOf()}&eventid=${eventid}&memberinfo=${memberinfo}&answerContent=${answer}`; gmRequest({ method: "get", url: url, headers: { 'Host': _h, 'Referer': vrefer, 'Sec-Fetch-Site': 'same-origin', 'User-Agent': navigator.userAgent }, timeout: 10000, onload: function(res) { try { if (res.status === 200) { let data = JSON.parse(res.responseText); resolve(data); } else { reject(new Error(`HTTP ${res.status}`)); } } catch (e) { reject(e); } }, onerror: function(err) { if (err.error && err.error.includes("connect list")) { const domain = err.error.match(/:\/\/(.[^/]+)/)[1]; const notice = `由于connect未添加导致无权限请求

请复制以下代码至脚本中的第19行位置
// @connect ${domain}`; logs.addLog(notice, 'red'); } else { logs.addLog(`请求报错[${url} - GET][${err.statusText || 'timeout'}]`, 'red'); } reject(err); }, ontimeout: function() { logs.addLog(`请求报错[${url} - GET][timeout]`, 'red'); reject(new Error('Request timeout')); } }); }); }, // 分析视频任务失败原因 analyzeVideoTaskFailure = (item, playTime, duration, isdrag, ispass) => { let reasons = []; let suggestions = []; // 1. 检查视频题目处理状态 if (videoQuestionEnabled) { reasons.push("视频题目:已启用自动处理 ✅"); // 检查是否有题目处理记录 let questionLogs = logs.getLogs().filter(log => log.includes('正在完成视频中的题目') || log.includes('视频内无提问') || log.includes('视频题目已完毕') ); if (questionLogs.length > 0) { reasons.push("题目处理:有处理记录 ✅"); } else { reasons.push("题目处理:无处理记录 ⚠️"); suggestions.push("检查视频是否真的有题目"); } } else { reasons.push("视频题目:未启用自动处理 ⚠️"); suggestions.push("建议开启视频题目自动处理功能"); } // 2. 检查学习时长 let durationPercent = (playTime / duration * 100).toFixed(1); if (playTime >= duration) { reasons.push(`学习时长:${playTime}秒 (要求:${duration}秒) ${durationPercent}% ✅`); } else { reasons.push(`学习时长:${playTime}秒 (要求:${duration}秒) ${durationPercent}% ❌`); suggestions.push("增加播放时长"); } // 3. 检查倍速设置 const activeRate = videoPlayMode === 'real' ? rateReal : rateSimulate; if (activeRate > 3) { reasons.push(`播放倍速:${activeRate}倍 (高风险) ⚠️⚠️`); suggestions.push("降低倍速到1-2倍"); } else if (activeRate > 2) { reasons.push(`播放倍速:${activeRate}倍 (可能被检测) ⚠️`); suggestions.push("建议降低倍速到1-2倍"); } else { reasons.push(`播放倍速:${activeRate}倍 ✅`); } // 4. 检查服务器响应状态 if (isdrag === '4') { reasons.push("服务器状态:isdrag=4 (时长足够但未确认完成)"); suggestions.push("开启复习模式重新播放"); } // 5. 检查任务通过状态 if (ispass) { if (ispass.isPassed === true) { reasons.push("任务状态:已通过验证 ✅"); } else { reasons.push("任务状态:未通过验证 ❌"); suggestions.push("检查是否有遗漏的交互要求"); } } else { reasons.push("任务状态:未知状态 ⚠️"); } // 6. 检查复习模式 if (_w.top.unrivalReviewMode === '1') { reasons.push("当前模式:复习模式 ✅"); } else { reasons.push("当前模式:普通模式"); suggestions.push("尝试开启复习模式"); } // 7. 检查网络状态 reasons.push("网络状态:正常 ✅"); // 8. 添加通用建议 if (suggestions.length === 0) { suggestions.push("1. 开启复习模式"); suggestions.push("2. 使用1倍速重新播放"); suggestions.push("3. 如果仍不通过,手动点击视频确认完成"); } return { reasons: reasons, suggestions: suggestions }; }, // ========== 11-D:模拟视频 Worker 计时 / ended 同步 / status 兜底 ========== xxtStopSimVideoWorker = () => { if (!simVideoWorker) { return; } try { simVideoWorker.aborted = true; try { simVideoWorker.postMessage({ type: 'stop' }); } catch (_) { } simVideoWorker.terminate(); } catch (_) { } simVideoWorker = null; }, xxtDispatchSimVideoEnded = (objectId) => { if (!objectId) { return; } const mediaIframes = _d.querySelectorAll( 'iframe.ans-insertvideo-online, iframe[src*="/ananas/modules/video/"], iframe[src*="/ananas/modules/audio/"]' ); for (let i = 0, l = mediaIframes.length; i < l; i++) { try { const dataAttr = mediaIframes[i].getAttribute('data'); if (!dataAttr) { continue; } const iframeData = JSON.parse(dataAttr); if (!iframeData || iframeData.objectid !== objectId) { continue; } const iframeDoc = mediaIframes[i].contentDocument || (mediaIframes[i].contentWindow && mediaIframes[i].contentWindow.document); if (!iframeDoc) { continue; } const mediaEl = iframeDoc.querySelector('video') || iframeDoc.querySelector('audio'); if (mediaEl) { mediaEl.dispatchEvent(new Event('ended', { bubbles: true })); logs.addLog('🎬 已触发 ended 事件,章节任务点 UI 将同步为已完成', 'green'); return; } logs.addLog('⚠️ iframe 内未找到 video/audio,任务点 UI 可能需刷新后才会变绿', 'orange'); return; } catch (e) { logs.addLog('⚠️ 触发 ended 事件失败:' + e.message, 'orange'); return; } } logs.addLog('⚠️ 未找到对应视频 iframe,任务点 UI 可能需刷新后才会变绿', 'orange'); }, xxtVerifySimPassedFallback = (ctx, worker, onDone) => { if (worker.aborted) { return; } const statusUrl = _p + '//' + _h + '/ananas/status/' + ctx.objectId + '?k=' + (FID || '') + '&_uid=' + ctx.uid + '&flag=normal&_dc=' + String(Math.round(new Date())); busyThread += 1; gmRequest({ method: 'get', headers: { 'Host': _h, 'Referer': vrefer, 'Sec-Fetch-Site': 'same-origin' }, url: statusUrl, onload: function (res) { busyThread -= 1; if (worker.aborted) { return; } try { const info = JSON.parse(res.responseText); if (info.isPassed) { logs.addLog('✅ 兜底查询确认任务已通过:' + ctx.name, 'green'); onDone(true, info); } else { logs.addLog('❌ 兜底查询确认仍未通过,可能受学时或防刷限制', 'red'); onDone(false, info); } } catch (e) { logs.addLog('⚠️ 兜底查询解析失败,继续下一任务', 'orange'); onDone(false, null); } }, onerror: function () { busyThread -= 1; if (worker.aborted) { return; } logs.addLog('⚠️ 兜底查询请求失败,继续下一任务', 'orange'); onDone(false, null); } }); }, xxtCreateSimulateReportWorker = (simCtx, item, dtype) => { const duration = simCtx.duration; const initialSec = simCtx.initialSec; const workerCode = [ 'var timer = null;', 'var duration = ' + duration + ';', 'var rate = ' + simCtx.rate + ';', 'var initialSec = ' + initialSec + ';', 'var reportInterval = ' + simCtx.reportInterval + ';', 'var lastReportTime = ' + initialSec + ';', 'var tickCount = 0;', 'var isRunning = true;', 'var wallStartMs = Date.now() - (' + initialSec + ' * 1000 / ' + simCtx.rate + ');', 'function getCurrentTime() {', ' var elapsed = (Date.now() - wallStartMs) / 1000;', ' return Math.min(Math.max(0, Math.ceil(elapsed * rate)), duration);', '}', 'function sendReport(playTime, isComplete, isFirst) {', ' self.postMessage({ type: "doReport", playTime: playTime, isComplete: isComplete, isFirst: !!isFirst });', '}', 'function updateLoop() {', ' if (!isRunning) return;', ' var currentTime = getCurrentTime();', ' var timeSinceLastReport = currentTime - lastReportTime;', ' var shouldReport = tickCount === 0 || timeSinceLastReport >= reportInterval || currentTime >= duration;', ' if (shouldReport) {', ' if (tickCount === 0 && initialSec === 0 && currentTime === 0) {', ' sendReport(0, false, true);', ' } else {', ' var reportTime = currentTime >= duration ? duration : Math.max(currentTime, 0);', ' sendReport(reportTime, currentTime >= duration, false);', ' }', ' lastReportTime = currentTime;', ' }', ' tickCount += 1;', ' self.postMessage({ type: "progress", currentTime: currentTime, duration: duration });', ' if (currentTime >= duration) {', ' isRunning = false;', ' if (timer) clearInterval(timer);', ' self.postMessage({ type: "completed" });', ' }', '}', 'timer = setInterval(updateLoop, 1000);', 'updateLoop();', 'self.onmessage = function(e) {', ' if (!e.data) return;', ' if (e.data.type === "stop") {', ' isRunning = false;', ' if (timer) clearInterval(timer);', ' self.postMessage({ type: "stopped" });', ' } else if (e.data.type === "updateRate") {', ' var virtualSec = getCurrentTime();', ' rate = e.data.rate;', ' wallStartMs = Date.now() - (virtualSec * 1000 / rate);', ' }', '};' ].join('\n'); const blob = new Blob([workerCode], { type: 'application/javascript' }); const worker = new Worker(URL.createObjectURL(blob)); worker.aborted = false; worker.isCompleted = false; worker.taskFinished = false; worker.videoContext = simCtx; worker._lastRate = simCtx.rate; const markSimVideoDone = (options) => { options = options || {}; if (worker.taskFinished || worker.aborted) { return; } worker.taskFinished = true; worker.isCompleted = true; if (options.triggerEnded) { xxtDispatchSimVideoEnded(item['objectId']); } missionList['m' + item['jobid']]['running'] = false; missionList['m' + item['jobid']]['done'] = true; xxtStopSimVideoWorker(); }; const handleSimPassed = () => { if (worker.isCompleted || worker.aborted) { return; } logs.addLog('✅ 视频任务已完成', 'green'); markSimVideoDone({ triggerEnded: true }); }; const handleSimCompleteFailure = (playTime, ispass) => { if (worker.taskFinished || worker.aborted) { return; } if (_w.top.unrivalReviewMode == '1') { logs.addLog('✅ 视频已观看完毕', 'green'); markSimVideoDone({ triggerEnded: false }); return; } const analysis = analyzeVideoTaskFailure(item, playTime, duration, '4', ispass); let mainReason = ''; if (analysis.reasons.some(r => r.includes('视频题目'))) { mainReason = ' - 可能原因:视频内提问未完成或学习通服务器未通过'; } else if (analysis.reasons.some(r => r.includes('观看时长'))) { mainReason = ' - 可能原因:观看时长不足或倍速过高'; } else if (analysis.reasons.some(r => r.includes('服务器'))) { mainReason = ' - 可能原因:学习通服务器未通过或网络问题'; } else { mainReason = ' - 可能原因:学习通服务器未通过'; } logs.addLog('[' + item['name'] + ']视频已观看完毕,但视频任务未完成' + mainReason, 'red'); const keyReasons = analysis.reasons.filter(r => r.includes('❌') || r.includes('⚠️⚠️') || r.includes('未通过') ); if (keyReasons.length > 0) { logs.addLog('🔍 关键问题:', 'orange'); keyReasons.forEach(reason => { logs.addLog('├─ ' + reason, 'orange'); }); } if (analysis.suggestions.length > 0) { logs.addLog('💡 建议操作:', 'blue'); analysis.suggestions.slice(0, 3).forEach((suggestion, index) => { logs.addLog((index + 1) + '. ' + suggestion, 'blue'); }); } markSimVideoDone({ triggerEnded: false }); }; worker.onmessage = function (e) { const data = e.data; if (!data || worker.aborted) { return; } switch (data.type) { case 'progress': rateSimulate = parseSimulateVideoRate(GM_getValue('unrivalrate_simulate', rateSimulate)); if (rateSimulate <= 0) { logs.addLog('⚠️ 奇怪的倍速,视频已自动跳过', 'orange'); xxtStopSimVideoWorker(); setTimeout(function () { missionList['m' + item['jobid']]['running'] = false; missionList['m' + item['jobid']]['done'] = true; }, 5000); return; } try { if (worker._lastRate !== rateSimulate) { worker._lastRate = rateSimulate; worker.postMessage({ type: 'updateRate', rate: rateSimulate }); } } catch (_) { } { const ct = data.currentTime; const cappedTime = duration ? Math.min(ct, duration) : ct; const percent = duration ? (cappedTime / duration) * 100 : 0; reportProgress('视频', item, percent, duration ? ('进度 ' + formatDuration(cappedTime) + ' / ' + formatDuration(duration)) : ('已观看 ' + formatDuration(cappedTime))); if (data.currentTime >= duration) { reportProgress('视频', item, 100, '视频任务已完成'); } } break; case 'doReport': { const ctx = worker.videoContext; let isdrag; if (data.isFirst) { isdrag = '3'; } else if (data.isComplete) { isdrag = '4'; } else { isdrag = '0'; } const playTime = data.playTime; const doReportRequest = (retryCount) => { if (worker.aborted) { return; } retryCount = retryCount || 0; const maxRetries = 1; const enc = generateSimulateEncLocal(ctx.classId, ctx.uid, ctx.jobId, ctx.objectId, playTime, ctx.duration); if (enc.length != 32) { stop = true; xxtStopSimVideoWorker(); return; } const rtVal = missionList['m' + item['jobid']]['rt']; const reportsUrl = reportUrl + '/' + ctx.dtoken + '?clazzId=' + ctx.classId + '&playingTime=' + playTime + '&duration=' + ctx.duration + '&clipTime=0_' + ctx.duration + '&objectId=' + ctx.objectId + '&otherInfo=' + ctx.otherInfo + '&jobid=' + ctx.jobId + '&userid=' + ctx.uid + '&isdrag=' + isdrag + '&view=pc&enc=' + enc + '&rt=' + rtVal + '&dtype=' + dtype + '&_t=' + String(Math.round(new Date())); busyThread += 1; gmRequest({ method: 'get', headers: { 'Host': _h, 'Referer': vrefer, 'Sec-Fetch-Site': 'same-origin', 'Content-Type': 'application/json' }, url: reportsUrl, onload: function (res) { busyThread -= 1; if (worker.aborted) { return; } try { const today = new Date(), todayStr = today.getFullYear() + 'd' + today.getMonth() + 'd' + today.getDate(), timelong = GM_getValue('unrivaltimelong', {}); if (timelong[ctx.uid] == undefined || timelong[ctx.uid]['today'] != todayStr) { timelong[ctx.uid] = { 'time': 0, 'today': todayStr }; } else { timelong[ctx.uid]['time']++; } GM_setValue('unrivaltimelong', timelong); if (timelong[ctx.uid]['time'] / 60 > 22 && item['doublespeed'] == 0 && _w.top.unrivalReviewMode == '0') { logs.addLog('今日学习时间过长,继续学习会导致清空进度,请明天再来', 'red'); setTimeout(function () { missionList['m' + item['jobid']]['running'] = false; missionList['m' + item['jobid']]['done'] = true; }, 5000); xxtStopSimVideoWorker(); return; } const ispass = JSON.parse(res.responseText); if (ispass['isPassed'] && _w.top.unrivalReviewMode == '0') { handleSimPassed(); return; } if (isdrag === '4') { if (retryCount < maxRetries) { logs.addLog('⚠️ 完成上报未通过(' + (retryCount + 1) + '/' + maxRetries + '),3秒后重试…', 'orange'); setTimeout(function () { doReportRequest(retryCount + 1); }, 3000); return; } xxtVerifySimPassedFallback(ctx, worker, function (passed) { if (worker.aborted) { return; } if (passed) { handleSimPassed(); } else { handleSimCompleteFailure(playTime, ispass); } }); return; } const totalDuration = ctx.duration || 0; const watchedMinutes = (playTime / 60).toFixed(1); const remainingSeconds = Math.max(totalDuration - playTime, 0); const remainingMinutes = (remainingSeconds / 60).toFixed(1); const progress = totalDuration > 0 ? ((playTime / totalDuration) * 100).toFixed(1) : '0.0'; logs.addLog(item['name'] + '已观看' + watchedMinutes + '分钟,剩余大约' + remainingMinutes + '分钟,完成' + progress + '%'); } catch (err) { console.log(err); if (res.responseText.indexOf('验证码') >= 0) { logs.addLog('已被超星风控,请点我处理,60秒后自动刷新页面', 'red'); missionList['m' + item['jobid']]['running'] = false; xxtStopSimVideoWorker(); stop = true; setTimeout(function () { _l.reload(); }, 60000); return; } logs.addLog('超星返回错误信息,十秒后重试,请重新登录或重新打开浏览器', 'red'); } }, onerror: function (err) { busyThread -= 1; if (worker.aborted) { return; } console.log(err); if (err.error && err.error.indexOf('@connect list') >= 0) { logs.addLog('请添加安全网址,将 【 //@connect ' + _h + ' 】方括号里的内容(不包括方括号)添加到脚本代码内指定位置,否则脚本无法正常运行,如图所示:', 'red'); logs.addLog(''); stop = true; } else if (isdrag === '4' && retryCount < maxRetries) { logs.addLog('⚠️ 完成上报失败,重试中(' + (retryCount + 1) + '/' + maxRetries + ')…', 'orange'); setTimeout(function () { doReportRequest(retryCount + 1); }, 3000); return; } else { logs.addLog('观看视频失败', 'red'); logs.addLog('错误原因:' + (err.error || 'unknown'), 'red'); } missionList['m' + item['jobid']]['running'] = false; xxtStopSimVideoWorker(); } }); }; doReportRequest(0); break; } case 'completed': logs.addLog('⏹️ 视频计时完成,等待服务器确认…', 'blue'); setTimeout(function () { if (worker.aborted || worker.taskFinished) { return; } logs.addLog('⚠️ 等待服务器确认超时,继续下一任务', 'orange'); if (_w.top.unrivalReviewMode == '1') { markSimVideoDone({ triggerEnded: false }); } else { handleSimCompleteFailure(duration, null); } }, 35000); break; case 'stopped': break; } }; return worker; }, // ========== 11-D end ========== finishVideoQuestions = async (item) => { try { logs.addLog(`[${item['name']}]获取视频中的题目`, 'info'); if (!item) { logs.addLog('视频对象为空', 'error'); return false; } const videoMid = (item['property'] && item['property']['mid']) || item['videoMid'] || ''; if (!videoMid) { if (!item['property']) { logs.addLog(`[${item['name']}]缺少视频 property(任务数据未传入,无法拉题)`, 'orange'); } else { logs.addLog(`[${item['name']}]缺少视频 mid,无法拉取题目`, 'orange'); logs.addLog(`property结构: ${JSON.stringify(item['property'], null, 2)}`, 'info'); } logs.addLog(`[${item['name']}]已跳过视频内题目`, 'info'); return true; } // 获取视频题目(cpi 来自 URL / otherInfo,不是 UID) const videoCpi = parseVideoCpi(item); let res = await initVideoQuestions(videoMid, videoCpi, classId, data && data['courseid']); if (!res || res.length === 0) { logs.addLog(`[${item['name']}]视频题目已完毕`, 'green'); return true; } const questionList = flattenVideoQuestionItems(res); if (!questionList.length) { logs.addLog(`[${item['name']}]视频题目解析失败(接口返回 ${res.length} 条,未找到可答题目)`, 'orange'); return true; } logs.addLog(`[${item['name']}]解析到 ${questionList.length} 道视频题`, 'info'); for (const item1 of questionList) { try { const resourceId = item1.resourceId || item1.eventid || item1.eventId; if (!item1.options || !item1.options.length) { logs.addLog('题目缺少选项,已跳过', 'orange'); continue; } if (!resourceId) { logs.addLog('题目缺少 resourceId/eventid,已跳过', 'orange'); continue; } const memberinfo = item1.memberinfo || item1.memberInfo || ''; const description = item1.description || item1.title || '视频内题目'; let answer = pickVideoQuestionAnswer(item1); let submitResult = null; if (answer) { const res1 = await submitVideoAnswer(classId, videoCpi, item['objectId'], resourceId, memberinfo, answer); if (res1 && res1.status) { submitResult = { answer: answer, res1: res1, description: description }; } } if (!submitResult) { submitResult = await trySubmitVideoQuestion(item, item1, videoCpi, answer); } if (submitResult) { const ansLabel = submitResult.res1.isRight ? '正确答案' : '尝试答案'; logs.addLog(`[正在完成视频中的题目]:${submitResult.description}
${ansLabel}: ${submitResult.answer}`, submitResult.res1.isRight ? 'green' : 'orange'); } else { logs.addLog(`[${description}]视频题目提交失败(已尝试全部选项)`, 'error'); } await new Promise(resolve => setTimeout(resolve, 1000)); } catch (e) { logs.addLog('处理视频题目异常: ' + (e.message || e), 'error'); } } logs.addLog(`[${item['name']}]视频题目已完毕`, 'green'); return true; } catch (e) { logs.addLog(`[${item['name']}]处理视频题目时出错: ${e.message}`, 'red'); logs.addLog(`视频对象: ${JSON.stringify(item, null, 2)}`, 'info'); return false; } }, // 真实播放:按 objectid 匹配 video/audio iframe // 返回 true 表示本函数已接管并完成;false 表示未命中,由调用方跳过(不切换到模拟模式) realPlayVideo = async (item) => { logs.addLog('🎥 真实播放模式:' + item['name'], 'blue'); const targetObjectId = item['objectId'] || (item['property'] && item['property']['objectid']) || ''; if (!targetObjectId) { logs.addLog('⚠️ 视频任务缺少 objectId,已跳过:' + item['name'], 'orange'); return false; } // 在 knowledge/cards 文档中按 objectid 匹配 iframe let targetIframe = null; let matchAttempts = 0; const maxMatchAttempts = 10; while (!targetIframe && matchAttempts < maxMatchAttempts) { const mediaIframes = document.querySelectorAll( 'iframe.ans-insertvideo-online, iframe[src*="/ananas/modules/video/"], iframe[src*="/ananas/modules/audio/"]' ); for (const f of mediaIframes) { try { const dataAttr = f.getAttribute('data'); if (!dataAttr) continue; const data = JSON.parse(dataAttr); if (data && data.objectid === targetObjectId) { targetIframe = f; break; } } catch (e) { // JSON.parse 失败,跳过该 iframe } } if (!targetIframe) { await new Promise(r => setTimeout(r, 500)); matchAttempts++; } } if (!targetIframe) { logs.addLog('⚠️ 未找到 objectid=' + targetObjectId.slice(0, 8) + '... 对应的媒体 iframe,已跳过:' + item['name'], 'orange'); return false; } const isAudioTask = !!(item['module'] && String(item['module']).includes('audio')); // 进入目标 iframe,等待 video 或 audio 元素出现 let mediaElement = null; let iframeDocument = null; let mediaAttempts = 0; const maxMediaAttempts = 20; while (!mediaElement && mediaAttempts < maxMediaAttempts) { try { iframeDocument = targetIframe.contentDocument || (targetIframe.contentWindow && targetIframe.contentWindow.document); if (iframeDocument) { mediaElement = iframeDocument.querySelector('video') || iframeDocument.querySelector('audio'); } } catch (e) { // 跨域,理论上 knowledge/cards 内同域应可访问 } if (!mediaElement) { await new Promise(r => setTimeout(r, 500)); mediaAttempts++; } } if (!mediaElement) { logs.addLog('⚠️ 媒体 iframe 内未找到 video/audio 元素,已跳过:' + item['name'], 'orange'); return false; } const isRealAudio = isAudioTask || mediaElement.tagName === 'AUDIO'; const mediaKind = isRealAudio ? '音频' : '视频'; const progressLabel = mediaKind; logs.addLog('✅ 命中' + mediaKind + ' objectid=' + targetObjectId.slice(0, 8) + '...,开始真实播放', 'green'); // 真实模式:V2 拉题并缓存正确答案,弹窗时再 DOM 点选(音频任务无视频内题,跳过) let videoQuestionCache = []; if (!isRealAudio && videoQuestionEnabled) { try { videoQuestionCache = await buildVideoQuestionCache(item); if (videoQuestionCache.length) { logs.addLog(`[${item['name']}]解析到 ${videoQuestionCache.length} 道视频题`, 'green'); } else { logs.addLog('未缓存到视频题答案,弹窗出现时将无法自动点选', 'orange'); } } catch (e) { logs.addLog('视频题目缓存失败:' + e.message, 'orange'); } } // 倍速:真实播放从独立配置读取(0.75~3,步进 0.25),开始播放后再应用 const applyRealPlaybackRate = (doc, mediaEl, targetRate) => { const applied = normalizeRealVideoRate(targetRate); try { mediaEl.playbackRate = applied; } catch (e) { } if (mediaEl.tagName !== 'AUDIO') { try { const vjsRoot = doc.getElementById('video'); const win = doc.defaultView || window; let player = vjsRoot && vjsRoot.player; if (!player && win.videojs && typeof win.videojs.getPlayer === 'function') { player = win.videojs.getPlayer('video'); } if (player && typeof player.playbackRate === 'function') { player.playbackRate(applied); } } catch (e) { } } return applied; }; // 自动静音(视频与音频/mp3 共用) if (autoMuteVideo) { try { mediaElement.muted = true; mediaElement.volume = 0; } catch (e) { } } // 视频内题目检测频率(毫秒)· 与 doVideo 内日志 "已启用(每5秒)" 保持一致 const QUESTION_CHECK_INTERVAL = 5000; // 视频内题目弹窗自动作答(progressMonitor 按 QUESTION_CHECK_INTERVAL 调用;busy 防并发重入) let videoQuestionBusy = false; const handleVideoQuestions = async () => { if (!videoQuestionEnabled || !iframeDocument || videoQuestionBusy) return; try { const submitBtn = iframeDocument.querySelector('#videoquiz-submit'); if (!submitBtn) return; const optionLabels = Array.from( iframeDocument.querySelectorAll('.ans-videoquiz-opt label') ); if (optionLabels.length === 0) return; videoQuestionBusy = true; let optionClicked = false; let matched = null; if (videoQuestionCache.length) { const stemEl = iframeDocument.querySelector('.tkTopic, .ans-videoquiz-tit, .videoquiz-title, .ans-videoquiz .title, .ans-videoquiz'); const stemText = stemEl ? stemEl.textContent : ''; matched = matchVideoQuestionFromCache(videoQuestionCache, stemText); if (matched && matched.answer) { optionClicked = clickVideoQuizOptionByAnswer(optionLabels, matched.answer); } } if (!optionClicked) { logs.addLog('视频内题目:未能匹配正确答案,请手动作答', 'orange'); videoQuestionBusy = false; return; } await new Promise(r => setTimeout(r, 500)); submitBtn.click(); await new Promise(r => setTimeout(r, 1000)); const container = iframeDocument.querySelector('#video .ans-videoquiz'); if (container) container.remove(); // 学习通题目弹窗会留下 .x-component-default 遮罩,统一隐藏以恢复播放 Array.from(iframeDocument.querySelectorAll('.x-component-default')) .forEach(com => { com.style.display = 'none'; }); const qDesc = (matched && matched.description) ? matched.description : '视频内题目'; const qAns = (matched && matched.answer) ? matched.answer : ''; logs.addLog(`[正在完成视频中的题目]:${qDesc}
正确答案: ${qAns}`, 'green'); } catch (e) { console.log('处理视频内题目失败:', e); } finally { videoQuestionBusy = false; } }; // 尝试播放(自动播放被禁用时点 vjs 播放按钮兜底) const tryPlay = async () => { try { await mediaElement.play(); return true; } catch (e) { const playBtns = []; try { playBtns.push(iframeDocument.querySelector('.vjs-big-play-button')); playBtns.push(iframeDocument.querySelector('.vjs-play-control')); if (isRealAudio) { playBtns.push(iframeDocument.querySelector('audio')); playBtns.push(iframeDocument.querySelector('.ans-insertaudio-online')); } } catch (err) { } for (const btn of playBtns) { if (btn) { btn.click(); await new Promise(r => setTimeout(r, 500)); if (!mediaElement.paused) return true; } } return false; } }; const playSuccess = await tryPlay(); if (!playSuccess) { logs.addLog('⚠️ ' + mediaKind + '无法自动播放,已跳过:' + item['name'], 'orange'); return false; } logs.addLog('✅ ' + mediaKind + '开始播放', 'green'); rateReal = normalizeRealVideoRate(GM_getValue('unrivalrate_real', rateReal)); const appliedRate = applyRealPlaybackRate(iframeDocument, mediaElement, rateReal); logs.addLog('真实播放倍速:' + appliedRate + 'x', 'blue'); // 真实播放:ended 标记完成;pause 满 REAL_PAUSE_RESUME_MS 仍暂停则续播;1s 轮询仅处理视频内题、进度日志与结束兜底 xxtStopRealPlayWatch(); let lastReportTime = 0; let lastQuestionCheckTime = 0; let lastPlayingAt = Date.now(); let pauseResumeTimer = null; let watchStopped = false; const clearPauseResumeTimer = () => { if (pauseResumeTimer) { clearTimeout(pauseResumeTimer); pauseResumeTimer = null; } }; const stopRealPlayWatch = (opts) => { opts = opts || {}; if (watchStopped) { return; } watchStopped = true; clearPauseResumeTimer(); try { mediaElement.removeEventListener('ended', onMediaEnded); mediaElement.removeEventListener('pause', onMediaPause); mediaElement.removeEventListener('playing', onMediaPlaying); } catch (_) { } if (realVideoProgressMonitor) { try { clearInterval(realVideoProgressMonitor); } catch (_) { } realVideoProgressMonitor = null; } realPlayWatch = null; if (opts.markDone) { reportProgress(progressLabel, item, 100, mediaKind + '任务已完成'); logs.addLog('✅ ' + mediaKind + '播放完成(任务点由学习通自身上报)', 'green'); missionList['m' + item['jobid']]['running'] = false; missionList['m' + item['jobid']]['done'] = true; } }; const tryResumeAfterPause = () => { if (watchStopped || mediaElement.ended || !mediaElement.paused) { return; } mediaElement.play().catch(e => { logs.addLog('恢复播放失败:' + e.message, 'orange'); }); }; const scheduleResumeIfStillPaused = () => { clearPauseResumeTimer(); pauseResumeTimer = setTimeout(() => { pauseResumeTimer = null; if (watchStopped || mediaElement.ended) { return; } if (mediaElement.paused) { tryResumeAfterPause(); } }, REAL_PAUSE_RESUME_MS); }; const onMediaPlaying = () => { lastPlayingAt = Date.now(); clearPauseResumeTimer(); }; const onMediaPause = () => { if (watchStopped || mediaElement.ended) { return; } scheduleResumeIfStillPaused(); }; const onMediaEnded = () => { stopRealPlayWatch({ markDone: true }); }; realPlayWatch = { cleanup: () => { stopRealPlayWatch({ markDone: false }); } }; mediaElement.addEventListener('playing', onMediaPlaying); mediaElement.addEventListener('pause', onMediaPause); mediaElement.addEventListener('ended', onMediaEnded); if (mediaElement.ended) { stopRealPlayWatch({ markDone: true }); return true; } realVideoProgressMonitor = setInterval(async () => { try { if (watchStopped) { return; } const now = Date.now(); if (!mediaElement.paused && !mediaElement.ended) { lastPlayingAt = now; } if (!mediaElement.ended && mediaElement.paused && (now - lastPlayingAt >= REAL_PAUSE_RESUME_MS) && !pauseResumeTimer) { scheduleResumeIfStillPaused(); } if (!isRealAudio && !videoQuestionBusy && now - lastQuestionCheckTime >= QUESTION_CHECK_INTERVAL) { lastQuestionCheckTime = now; await handleVideoQuestions(); } const currentTime = Math.floor(mediaElement.currentTime); const duration = Math.floor(mediaElement.duration); if (currentTime - lastReportTime >= reportInterval) { const percent = duration > 0 ? (currentTime / duration) * 100 : 0; const watchedMinutes = (currentTime / 60).toFixed(1); const remainingMinutes = ((duration - currentTime) / 60).toFixed(1); const progressText = '真实播放中 (' + formatDuration(currentTime) + ' / ' + formatDuration(duration) + ')'; reportProgress(progressLabel, item, percent, progressText); lastReportTime = currentTime; logs.addLog(item['name'] + '已观看' + watchedMinutes + '分钟,剩余大约' + remainingMinutes + '分钟,完成' + percent.toFixed(1) + '%', 'blue'); } if (!watchStopped && (mediaElement.ended || (duration > 0 && currentTime >= duration - 1))) { stopRealPlayWatch({ markDone: true }); } } catch (e) { stopRealPlayWatch({ markDone: true }); logs.addLog('真实播放监听异常,已跳过:' + e.message, 'red'); } }, 1000); return true; }, doVideo = async (item) => { const missionKey = 'm' + item['jobid']; if (!missionList[missionKey]) return; if (missionList[missionKey]['running'] || missionList[missionKey]['done']) return; missionList[missionKey]['running'] = true; let videojs_id = String(parseInt(Math.random() * 9999999)); _d.cookie = 'videojs_id=' + videojs_id + ';path=/' // 初始化进度条 reportProgress('视频', item, 0, item['duration'] ? ('准备开始 (00:00 / ' + formatDuration(item['duration']) + ')') : '准备开始...'); // 真实/模拟模式分流:real 走 realPlayVideo;simulate 走下方接口上报 // real 未命中则跳过该任务,不自动降级为模拟 if (videoPlayMode === 'real') { logs.addLog('当前模式:真实播放模式', 'blue'); if (videoQuestionEnabled) { logs.addLog('视频内题目:已启用(每5秒)', 'green'); } else { logs.addLog('视频内题目:已关闭', 'info'); } const realPlaySuccess = await realPlayVideo(item); if (realPlaySuccess) { return; } // 未命中 iframe 则跳过该任务 logs.addLog('真实播放模式未命中页面 video/audio 元素,本任务跳过;如需脚本完成该任务点请切换为「模拟模式」后再试。', 'orange'); missionList['m' + item['jobid']]['running'] = false; missionList['m' + item['jobid']]['done'] = true; return; } // 模拟模式:接口上报进度 logs.addLog('当前模式:模拟模式(不实际播放视频)', 'blue'); rateSimulate = parseSimulateVideoRate(GM_getValue('unrivalrate_simulate', rateSimulate)); if (rateSimulate <= 0) { logs.addLog('⚠️ 奇怪的倍速,视频已自动跳过', 'orange'); setTimeout(function () { missionList['m' + item['jobid']]['running'] = false; missionList['m' + item['jobid']]['done'] = true; }, 5000); return; } if (videoQuestionEnabled) { logs.addLog('开始处理视频题目(接口模式):' + item['name'], 'blue'); try { await finishVideoQuestions(item); } catch (e) { logs.addLog('处理视频题目时出错,继续播放视频:' + e.message, 'orange'); } } else { logs.addLog('视频题目自动处理功能已关闭,跳过题目处理', 'info'); } // 模拟模式固定从 0 秒上报,不使用 mArg/iframe 已学进度作为起点 const initialSec = 0; if (_w.top.unrivalReviewMode == '1' && item['isPassed']) { logs.addLog('复习模式:已完成任务从头重刷(模拟模式)', 'green'); } reportProgress('视频', item, 0, item['duration'] ? ('准备开始 (00:00 / ' + formatDuration(item['duration']) + ')') : '准备开始...'); logs.addLog('🎬 开始刷视频:' + item['name'] + ',倍速:' + String(rateSimulate) + '倍', 'blue'); logs.addLog('视频观看信息每' + reportInterval + '秒上报一次,请耐心等待', 'green'); logs.addLog('如遇脚本使用异常情况,请检查脚本版本是否为最新版,点我(脚本猫)点我(greasyfork)检查', 'orange'); if (disableMonitor) { logs.addLog('解除多端学习监控有清除进度风险,请谨慎使用', 'orange'); } let dtype = 'Video'; if (item['module'].includes('audio')) { dtype = 'Audio'; rt = ''; } xxtStopSimVideoWorker(); logs.addLog('📡 模拟模式使用 Web Worker 后台计时(建议同时开启「激活挂机」)', 'blue'); simVideoWorker = xxtCreateSimulateReportWorker({ name: item['name'], duration: item['duration'], initialSec: initialSec, rate: rateSimulate, reportInterval: reportInterval, dtoken: item['dtoken'], objectId: item['objectId'], jobId: item['jobid'], otherInfo: item['otherInfo'], classId: classId, uid: UID }, item, dtype); }, doDocument = (item) => { missionList['m' + item['jobid']]['running'] = true; logs.addLog('开始刷文档:' + item['name']); setTimeout(function () { busyThread += 1; gmRequest({ method: "get", url: _p + '//' + _h + '/ananas/job/document?jobid=' + item['jobid'] + '&knowledgeid=' + chapterId + '&courseid=' + courseId + '&clazzid=' + classId + '&jtoken=' + item['jtoken'], onload: function (res) { try { busyThread -= 1; let ispass = JSON.parse(res.responseText); if (ispass['status']) { logs.addLog('文档任务已完成', 'green'); } else { logs.addLog('文档已阅读完成,但任务点未完成', 'red'); } } catch (err) { console.log(err); console.log(res.responseText); logs.addLog('解析文档内容失败', 'red'); } missionList['m' + item['jobid']]['running'] = false; missionList['m' + item['jobid']]['done'] = true; }, onerror: function (err) { console.log(err); if (err.error.indexOf('@connect list') >= 0) { logs.addLog('请添加安全网址,将 【 //@connect ' + _h + ' 】方括号里的内容(不包括方括号)添加到脚本代码内指定位置,否则脚本无法正常运行,如图所示:', 'red'); logs.addLog( '' ); stop = true; } else { logs.addLog('阅读文档失败', 'red'); logs.addLog('错误原因:' + err.error, 'red'); } missionList['m' + item['jobid']]['running'] = false; missionList['m' + item['jobid']]['done'] = true; } }); }, parseInt(Math.random() * 2000 + 9000, 10)) }, doWork = (item) => { missionList['m' + item['jobid']]['running'] = true; logs.addLog('开始刷章节测试:' + item['name'], 'green'); logs.addLog('您设置的答题正确率为:' + String(accuracy) + '%,只有在高于此正确率时才会提交测试', 'blue'); top.document.getElementById('workPanel').style.display = 'block'; top.document.getElementById('frame_content').src = _p + '//' + _h + '/work/phone/work?workId=' + item['jobid'] .replace('work-', '') + '&courseId=' + courseId + '&clazzId=' + classId + '&knowledgeId=' + chapterId + '&jobId=' + item['jobid'] + '&enc=' + item['enc']; _w.top.unrivalWorkInfo = ''; _w.top.unrivalDoneWorkId = ''; // 章节测验日志轮询:模块级单例,避免重复 doWork 时泄漏 if (workInfoLoop) { try { clearInterval(workInfoLoop); } catch (_) { } } workInfoLoop = setInterval(function () { if (_w.top.unrivalWorkInfo != '') { logs.addLog(_w.top.unrivalWorkInfo); _w.top.unrivalWorkInfo = ''; } }, 100); if (workCheckcross) { try { clearInterval(workCheckcross); } catch (_) { } workCheckcross = null; } if (workDoneInterval) { try { clearInterval(workDoneInterval); } catch (_) { } workDoneInterval = null; } workCheckcross = setInterval(function () { if (_w.top.unrivalWorkDone == false) { clearInterval(workCheckcross); workCheckcross = null; return; } let ifW = top.document.getElementById('frame_content').contentWindow; try { ifW.location.href; } catch (e) { console.log(e); if (e.message.indexOf('cross-origin') != -1) { clearInterval(workCheckcross); workCheckcross = null; _w.top.unrivalWorkDone = true; return; } } }, 2000); workDoneInterval = setInterval(function () { if (_w.top.unrivalWorkDone) { _w.top.unrivalWorkDone = false; clearInterval(workDoneInterval); workDoneInterval = null; // 测验完成后清理定时器,并将 iframe 置为 about:blank 以释放内存 if (workInfoLoop) { try { clearInterval(workInfoLoop); } catch (_) { } workInfoLoop = null; } try { clearInterval(workCheckcross); workCheckcross = null; } catch (_) { } _w.top.unrivalDoneWorkId = ''; top.document.getElementById('workPanel').style.display = 'none'; top.document.getElementById('frame_content').src = 'about:blank'; setTimeout(function () { missionList['m' + item['jobid']]['running'] = false; missionList['m' + item['jobid']]['done'] = true; }, 5000); } }, 500); }, missionList = []; try { top._xxtStudyAddLog = (msg, color) => logs.addLog(msg, color || 'green'); } catch (_) { } xxtClearJoblistPanel(); xxtResetWorkPanel(); xxtResetVideoTimePanel(); try { progressTracker.reset('等待任务开始'); } catch (_) { } if (jobList.length <= 0) { if (jumpType != 2) { xxtTryNoTaskJump('ℹ️ 此页无任务,准备跳转下一节'); } else { logs.addLog('此页无任务,用户设置为不跳转,脚本已结束运行,如需自动跳转,请到任务配置界面改变模式为遍历', 'green'); } return; } for (let i = 0, l = jobList.length; i < l; i++) { let item = jobList[i]; if (item['type'] == 'video') { video_getReady(item); } else if (item['type'] == 'document') { missionList['m' + item['jobid']] = { 'type': 'document', 'jtoken': item['jtoken'], 'jobid': item['jobid'], 'name': item['property']['name'], 'done': false, 'running': false }; top.document.getElementById('joblist').innerHTML += `
` + '[文档]' + item['property']['name'] + `
` } else if (item['type'] == 'workid' && _w.top.unrivalDoWork == '1') { missionList['m' + item['jobid']] = { 'type': 'work', 'workid': item['property']['workid'], 'jobid': item['jobid'], 'name': item['property']['title'], 'enc': item['enc'], 'done': false, 'running': false }; top.document.getElementById('joblist').innerHTML += `
` + '[章节测试]' + item['property']['title'] + `
` } else { try { let jobName = item['property']['name']; if (jobName == undefined) { jobName = item['property']['title']; } top.document.getElementById('joblist').innerHTML += `
` + '已跳过:' + jobName + `
` } catch (e) { } } } loopjob(); } else if (_l.href.includes("mycourse/studentstudy")) { xxtMountHelperFloatWindow(); xxtClearTimersByPrefix('study'); var audiofile = 'data:audio/ogg;base64,T2dnUwACAAAAAAAAAABwRPFFAAAAAGFtEqwBHgF2b3JiaXMAAAAAAUAfAAAAAAAAUHgAAAAAAACZAU9nZ1MAAAAAAAAAAAAAcETxRQEAAAA7J4IBDP8F////////////tQN2b3JiaXMvAAAAWGlwaC5PcmcgbGliVm9yYmlzIEkgMjAxNDAxMjIgKFR1cnBha8OkcsOkamlpbikGAAAAJQAAAEVOQ09ERVI9U291bmQgU3R1ZGlvLCBsaWJWb3JiaXMgMS4zLjEbAAAAQUxCVU0gQVJUSVNUPUFkdmVudHVyZSBMYW5kFAAAAEFMQlVNPUFkdmVudHVyZSBMYW5kIQAAAEVOQ09ESU5HIEFQUExJQ0FUSU9OPVNvdW5kIFN0dWRpbxUAAABBUlRJU1Q9QWR2ZW50dXJlIExhbmQjAAAAVElUTEU9RW1wdHkgTG9vcCBGb3IgSlMgUGVyZm9ybWFuY2UBBXZvcmJpcxJCQ1YBAAABAAxSFCElGVNKYwiVUlIpBR1jUFtHHWPUOUYhZBBTiEkZpXtPKpVYSsgRUlgpRR1TTFNJlVKWKUUdYxRTSCFT1jFloXMUS4ZJCSVsTa50FkvomWOWMUYdY85aSp1j1jFFHWNSUkmhcxg6ZiVkFDpGxehifDA6laJCKL7H3lLpLYWKW4q91xpT6y2EGEtpwQhhc+211dxKasUYY4wxxsXiUyiC0JBVAAABAABABAFCQ1YBAAoAAMJQDEVRgNCQVQBABgCAABRFcRTHcRxHkiTLAkJDVgEAQAAAAgAAKI7hKJIjSZJkWZZlWZameZaouaov+64u667t6roOhIasBADIAAAYhiGH3knMkFOQSSYpVcw5CKH1DjnlFGTSUsaYYoxRzpBTDDEFMYbQKYUQ1E45pQwiCENInWTOIEs96OBi5zgQGrIiAIgCAACMQYwhxpBzDEoGIXKOScggRM45KZ2UTEoorbSWSQktldYi55yUTkompbQWUsuklNZCKwUAAAQ4AAAEWAiFhqwIAKIAABCDkFJIKcSUYk4xh5RSjinHkFLMOcWYcowx6CBUzDHIHIRIKcUYc0455iBkDCrmHIQMMgEAAAEOAAABFkKhISsCgDgBAIMkaZqlaaJoaZooeqaoqqIoqqrleabpmaaqeqKpqqaquq6pqq5seZ5peqaoqp4pqqqpqq5rqqrriqpqy6ar2rbpqrbsyrJuu7Ks256qyrapurJuqq5tu7Js664s27rkearqmabreqbpuqrr2rLqurLtmabriqor26bryrLryratyrKua6bpuqKr2q6purLtyq5tu7Ks+6br6rbqyrquyrLu27au+7KtC7vourauyq6uq7Ks67It67Zs20LJ81TVM03X9UzTdVXXtW3VdW1bM03XNV1XlkXVdWXVlXVddWVb90zTdU1XlWXTVWVZlWXddmVXl0XXtW1Vln1ddWVfl23d92VZ133TdXVblWXbV2VZ92Vd94VZt33dU1VbN11X103X1X1b131htm3fF11X11XZ1oVVlnXf1n1lmHWdMLqurqu27OuqLOu+ruvGMOu6MKy6bfyurQvDq+vGseu+rty+j2rbvvDqtjG8um4cu7Abv+37xrGpqm2brqvrpivrumzrvm/runGMrqvrqiz7uurKvm/ruvDrvi8Mo+vquirLurDasq/Lui4Mu64bw2rbwu7aunDMsi4Mt+8rx68LQ9W2heHVdaOr28ZvC8PSN3a+AACAAQcAgAATykChISsCgDgBAAYhCBVjECrGIIQQUgohpFQxBiFjDkrGHJQQSkkhlNIqxiBkjknIHJMQSmiplNBKKKWlUEpLoZTWUmotptRaDKG0FEpprZTSWmopttRSbBVjEDLnpGSOSSiltFZKaSlzTErGoKQOQiqlpNJKSa1lzknJoKPSOUippNJSSam1UEproZTWSkqxpdJKba3FGkppLaTSWkmptdRSba21WiPGIGSMQcmck1JKSamU0lrmnJQOOiqZg5JKKamVklKsmJPSQSglg4xKSaW1kkoroZTWSkqxhVJaa63VmFJLNZSSWkmpxVBKa621GlMrNYVQUgultBZKaa21VmtqLbZQQmuhpBZLKjG1FmNtrcUYSmmtpBJbKanFFluNrbVYU0s1lpJibK3V2EotOdZaa0ot1tJSjK21mFtMucVYaw0ltBZKaa2U0lpKrcXWWq2hlNZKKrGVklpsrdXYWow1lNJiKSm1kEpsrbVYW2w1ppZibLHVWFKLMcZYc0u11ZRai621WEsrNcYYa2415VIAAMCAAwBAgAlloNCQlQBAFAAAYAxjjEFoFHLMOSmNUs45JyVzDkIIKWXOQQghpc45CKW01DkHoZSUQikppRRbKCWl1losAACgwAEAIMAGTYnFAQoNWQkARAEAIMYoxRiExiClGIPQGKMUYxAqpRhzDkKlFGPOQcgYc85BKRljzkEnJYQQQimlhBBCKKWUAgAAChwAAAJs0JRYHKDQkBUBQBQAAGAMYgwxhiB0UjopEYRMSielkRJaCylllkqKJcbMWomtxNhICa2F1jJrJcbSYkatxFhiKgAA7MABAOzAQig0ZCUAkAcAQBijFGPOOWcQYsw5CCE0CDHmHIQQKsaccw5CCBVjzjkHIYTOOecghBBC55xzEEIIoYMQQgillNJBCCGEUkrpIIQQQimldBBCCKGUUgoAACpwAAAIsFFkc4KRoEJDVgIAeQAAgDFKOSclpUYpxiCkFFujFGMQUmqtYgxCSq3FWDEGIaXWYuwgpNRajLV2EFJqLcZaQ0qtxVhrziGl1mKsNdfUWoy15tx7ai3GWnPOuQAA3AUHALADG0U2JxgJKjRkJQCQBwBAIKQUY4w5h5RijDHnnENKMcaYc84pxhhzzjnnFGOMOeecc4wx55xzzjnGmHPOOeecc84556CDkDnnnHPQQeicc845CCF0zjnnHIQQCgAAKnAAAAiwUWRzgpGgQkNWAgDhAACAMZRSSimllFJKqKOUUkoppZRSAiGllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimVUkoppZRSSimllFJKKaUAIN8KBwD/BxtnWEk6KxwNLjRkJQAQDgAAGMMYhIw5JyWlhjEIpXROSkklNYxBKKVzElJKKYPQWmqlpNJSShmElGILIZWUWgqltFZrKam1lFIoKcUaS0qppdYy5ySkklpLrbaYOQelpNZaaq3FEEJKsbXWUmuxdVJSSa211lptLaSUWmstxtZibCWlllprqcXWWkyptRZbSy3G1mJLrcXYYosxxhoLAOBucACASLBxhpWks8LR4EJDVgIAIQEABDJKOeecgxBCCCFSijHnoIMQQgghREox5pyDEEIIIYSMMecghBBCCKGUkDHmHIQQQgghhFI65yCEUEoJpZRSSucchBBCCKWUUkoJIYQQQiillFJKKSGEEEoppZRSSiklhBBCKKWUUkoppYQQQiillFJKKaWUEEIopZRSSimllBJC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', audioPlayer = new Audio(audiofile); _w.top.backNow = 0; audioPlayer.loop = true; _w.audioPlayer = audioPlayer; xxtSetInterval(function () { try { _w.jQuery.fn.viewer.Constructor.prototype.show = () => { }; } catch (e) { } }, 1000, 'study.loop.viewerPatch'); try { _w.unrivalScriptList.push('Fuck me please'); } catch (e) { _w.unrivalScriptList = ['Fuck me please']; } function checkOffline() { let dleft = _d.getElementsByClassName('left'); if (dleft.length == 1) { let img = dleft[0].getElementsByTagName('img'); if (img.length == 1) { if (img[0].src.indexOf('loading.gif') != -1) { return true; } } } return false; } xxtSetInterval(function () { if (checkOffline()) { setTimeout(function () { if (checkOffline()) { _l.reload(); } }, 10000) } }, 3000, 'study.loop.offlineCheck'); _d.addEventListener('visibilitychange', function () { var c = 0; if (_w.top.backNow == 0) { _d.title = '⚠️请先激活挂机'; return } else { _d.title = '学生学习页面'; } if (_d.hidden) { audioPlayer.play(); var timer = xxtSetInterval(function () { if (c) { _d.title = '挂机中'; c = 0; } else { _d.title = '挂机中'; c = 1; } if (!_d.hidden) { xxtClearInterval(timer); _d.title = '学生学习页面'; } }, 1300, 'study.ephemeral.titleBlink'); } else { audioPlayer.pause(); } }); _w.unrivalgetTeacherAjax = _w.getTeacherAjax; _w.getTeacherAjax = (courseid, classid, cid) => { if (cid == getQueryVariable('chapterId')) { return; } xxtClearTimersByPrefix('study.ephemeral'); xxtResetWorkPanel(); xxtResetVideoTimePanel(); try { progressTracker.reset('等待任务开始'); } catch (_) { } _w.top.unrivalPageRd = ''; _w.unrivalgetTeacherAjax(courseid, classid, cid); } if (disableMonitor == 1) { _w.appendChild = _w.Element.prototype.appendChild; _w.Element.prototype.appendChild = function () { try { if (arguments[0].src.indexOf('detect.chaoxing.com') > 0) { return; } } catch (e) { } _w.appendChild.apply(this, arguments); }; } _w.jump = false; xxtSetInterval(function () { if (getQueryVariable('mooc2') == '1') { let tabs = _d.getElementsByClassName('posCatalog_select'); for (let i = 0, l = tabs.length; i < l; i++) { let tabId = tabs[i].getAttribute('id'); if (tabId && tabId.indexOf('cur') >= 0 && tabs[i].getAttribute('class') == 'posCatalog_select') { tabs[i].setAttribute('onclick', "getTeacherAjax('" + courseId + "','" + classId + "','" + tabId.replace('cur', '') + "');"); } } } else { let h4s = _d.getElementsByTagName('h4'), h5s = _d.getElementsByTagName('h5'); for (let i = 0, l = h4s.length; i < l; i++) { const h4Id = h4s[i].getAttribute('id'); if (h4Id && h4Id.indexOf('cur') >= 0) { h4s[i].setAttribute('onclick', "getTeacherAjax('" + courseId + "','" + classId + "','" + h4Id.replace('cur', '') + "');"); } } for (let i = 0, l = h5s.length; i < l; i++) { const h5Id = h5s[i].getAttribute('id'); if (h5Id && h5Id.indexOf('cur') >= 0) { h5s[i].setAttribute('onclick', "getTeacherAjax('" + courseId + "','" + classId + "','" + h5Id.replace('cur', '') + "');"); } } } }, 1000, 'study.loop.onclickPatch'); xxtSetInterval(function () { if (_w.jump) { _w.jump = false; if (!xxtHasNextChapter()) { stop = true; xxtNotifyChapterEnd(); return; } _w.top.unrivalDoneWorkId = ''; _w.jjump = (rd) => { if (rd != _w.top.unrivalPageRd) { return; } try { setTimeout(function () { let nextBtn = xxtGetNextChapterBtn(_d); if (!nextBtn) { stop = true; xxtNotifyChapterEnd(); return; } try { setTimeout(function () { if (rd != _w.top.unrivalPageRd) { return; } // 新版学习通:点击前先检查并处理弹窗 if (getQueryVariable('mooc2') == '1') { const warn = document.querySelector('.popDiv.wid440.popMove .jobLimitTip'); if (warn && /当前章节还有任务点未完成,是否去完成/.test(warn.innerText)) { console.log('[学习通助手] 检测到未完成任务提示,自动点击下一节'); const nextBtnPop = document.querySelector('.popDiv.wid440.popMove .nextChapter'); if (nextBtnPop) { nextBtnPop.click(); return; } } } nextBtn = xxtGetNextChapterBtn(_d); if (nextBtn) { nextBtn.click(); } // 旧版学习通:点击后检查弹窗是否出现 if (getQueryVariable('mooc2') != '1') { setTimeout(function () { const tip = document.getElementById('jobFinishTip'); if (tip && tip.style.display !== 'none') { const nextBtnTip = tip.querySelector('.nextChapter'); if (nextBtnTip) nextBtnTip.click(); } }, 500); } }, 2000); } catch (e) { } }, 2000); } catch (e) { } } _w.onReadComplete1(); setTimeout('jjump("' + _w.top.unrivalPageRd + '")', 2856); } }, 200, 'study.loop.jumpLoop'); } else if (_l.href.indexOf("work/phone/doHomeWork") > 0) { var wIdE = _d.getElementById('workLibraryId') || _d.getElementById('oldWorkId'), wid = wIdE.value; _w.top.unrivalWorkDone = false; const xxtOnWorkSaved = () => { _w.top.unrivalDoneWorkId = getQueryVariable('workId'); }; if (_w.top.unrivalDoneWorkId == getQueryVariable('workId')) { _w.top.unrivalWorkDone = true; return; } _w.confirm = (msg) => { return true; } var questionList = [], questionsElement = _d.getElementsByClassName('Py-mian1'), questionNum = questionsElement.length, foundCount = 0, totalQuestionNum = 0, ctHasWorkAnswer = (text) => { const s = String(text || '').trim(); return !!s && !/\(未搜索到答案\)|未搜索到答案|未找到答案,可点击/.test(s); }, ctWorkQid = (quu) => quu['questionid'].replace('answer', ''), ctGetSelectedLetters = (quu, choiceEs) => { const qid = ctWorkQid(quu); const letters = []; for (let i = 0, k = choiceEs.length; i < k; i++) { const li = choiceEs[i]; if (li.getAttribute('id-param') != qid) continue; if (!li.getAttribute('class') || !li.getAttribute('class').includes('cur')) continue; const emEl = li.getElementsByTagName('em')[0]; if (!emEl) continue; const letter = emEl.getAttribute('id-param') || ''; if (letter) letters.push(letter); } return letters.sort().join(''); }, ctGetJudgePageLabel = (quu, choiceEs) => { const qid = ctWorkQid(quu); for (let i = 0, k = choiceEs.length; i < k; i++) { const li = choiceEs[i]; if (li.getAttribute('id-param') != qid) continue; if (!li.getAttribute('class') || !li.getAttribute('class').includes('cur')) continue; const vp = li.getAttribute('val-param'); if (vp === 'true') return '正确'; if (vp === 'false') return '错误'; const emEl = li.getElementsByTagName('em')[0]; if (emEl) { const t = (emEl.textContent || emEl.getAttribute('id-param') || '').trim(); if (t) return t; } } return ''; }, ctJudgeValFromResult = (result) => { if ('正确是对√Tri'.indexOf(result) >= 0) return 'true'; if ('错误否错×Fwr'.indexOf(result) >= 0) return 'false'; return ''; }, ctNormOpt = (s) => trim(s).replace(/\s/ig, ''), ctResolveExpectLetters = (quu, result) => { const raw = trim(result); if (!raw) { return { letters: '', matched: false }; } if (quu['type'] === '单选题') { const letterUp = raw.toUpperCase(); if (/^[A-Z]$/.test(letterUp)) { for (let option in quu['options']) { if (quu['options'][option]['value'] === letterUp) { return { letters: letterUp, matched: true }; } } } for (let option in quu['options']) { const optNorm = ctNormOpt(option); const ansNorm = ctNormOpt(raw); if (optNorm == ansNorm || optNorm.includes(ansNorm) || ansNorm.includes(optNorm)) { return { letters: quu['options'][option]['value'], matched: true }; } } return { letters: '', matched: false }; } if (quu['type'] === '多选题') { const letterOnly = raw.replace(/[,,、\s]/g, '').toUpperCase(); if (/^[A-Z]+$/.test(letterOnly)) { let allValid = letterOnly.length > 0; for (let i = 0; i < letterOnly.length; i++) { let found = false; for (let option in quu['options']) { if (quu['options'][option]['value'] === letterOnly[i]) { found = true; break; } } if (!found) { allValid = false; break; } } if (allValid) { return { letters: letterOnly.split('').sort().join(''), matched: true }; } } const answerData = ctNormOpt(raw); const letters = []; for (let option in quu['options']) { if (answerData.includes(ctNormOpt(option))) { letters.push(quu['options'][option]['value']); } } if (letters.length > 0) { return { letters: letters.sort().join(''), matched: true }; } return { letters: '', matched: false }; } return { letters: '', matched: false }; }, ctBuildSearchPayload = (quu) => { return { question: quu['question'] }; }, ctClickLetters = (quu, letters, choiceEs) => { const qid = ctWorkQid(quu); let clicked = false; for (let i = 0; i < letters.length; i++) { const letter = letters[i]; for (let y = 0, j = choiceEs.length; y < j; y++) { if (choiceEs[y].getElementsByTagName('em').length < 1) { continue; } if (choiceEs[y].getElementsByTagName('em')[0].getAttribute('id-param') == letter && choiceEs[y].getAttribute('id-param') == qid) { if (!choiceEs[y].getAttribute('class').includes('cur')) { choiceEs[y].click(); clicked = true; } break; } } } return clicked; }, ctPageMatchesIcodef = (quu, result, choiceEs) => { if (!ctHasWorkAnswer(result)) return false; const qid = ctWorkQid(quu); if (quu['type'] === '判断题') { const expect = ctJudgeValFromResult(result); if (!expect) return false; for (let i = 0, k = choiceEs.length; i < k; i++) { const li = choiceEs[i]; if (li.getAttribute('id-param') != qid) continue; if (!li.getAttribute('class') || !li.getAttribute('class').includes('cur')) continue; return li.getAttribute('val-param') === expect; } return false; } if (quu['type'] === '单选题' || quu['type'] === '多选题') { const resolved = ctResolveExpectLetters(quu, result); if (!resolved.matched) return false; return ctGetSelectedLetters(quu, choiceEs) === resolved.letters; } return false; }, ctApplyIcodefAnswer = (quu, result, choiceEs) => { const emptyApply = { hit: false, skipClick: false, pageLabel: '', selectedOption: '', syncedFrom: '' }; if (!ctHasWorkAnswer(result)) { return emptyApply; } if (quu['type'] === '判断题') { const prevLabel = ctGetJudgePageLabel(quu, choiceEs); if (ctPageMatchesIcodef(quu, result, choiceEs)) { const pageLabel = prevLabel || (ctJudgeValFromResult(result) === 'true' ? '正确' : '错误'); return { hit: true, skipClick: true, pageLabel: pageLabel, selectedOption: '', syncedFrom: '' }; } const answer = ctJudgeValFromResult(result); if (!answer) { return emptyApply; } for (let u = 0, k = choiceEs.length; u < k; u++) { if (choiceEs[u].getAttribute('val-param') == answer && choiceEs[u].getAttribute('id-param') == ctWorkQid(quu)) { if (!choiceEs[u].getAttribute('class') || !choiceEs[u].getAttribute('class').includes('cur')) { choiceEs[u].click(); } if (ctPageMatchesIcodef(quu, result, choiceEs)) { const newLabel = ctGetJudgePageLabel(quu, choiceEs) || (answer === 'true' ? '正确' : '错误'); const emEl = choiceEs[u].getElementsByTagName('em')[0]; let selectedOption = ''; if (emEl) { selectedOption = emEl.getAttribute('id-param') || emEl.textContent || ''; } const syncedFrom = prevLabel && prevLabel !== newLabel ? prevLabel : ''; return { hit: true, skipClick: false, pageLabel: newLabel, selectedOption: selectedOption, syncedFrom: syncedFrom }; } break; } } return emptyApply; } const resolved = ctResolveExpectLetters(quu, result); if (!resolved.matched) { return emptyApply; } const prevLetters = ctGetSelectedLetters(quu, choiceEs); if (prevLetters === resolved.letters) { return { hit: true, skipClick: true, pageLabel: resolved.letters, selectedOption: '', syncedFrom: '' }; } ctClickLetters(quu, resolved.letters, choiceEs); if (ctGetSelectedLetters(quu, choiceEs) === resolved.letters) { const syncedFrom = prevLetters && prevLetters !== resolved.letters ? prevLetters : ''; return { hit: true, skipClick: false, pageLabel: '', selectedOption: resolved.letters, syncedFrom: syncedFrom }; } return emptyApply; }, CT_LOG = { header: 'blue', question: '#444444', ok: '#2e7d32', syncNote: '#e65100', warn: '#ef6c00', fail: '#c62828', random: '#a16207', muted: '#616161' }, ctSpan = (color, text) => '' + text + '', ctSplitHumorNoAnswerLine = (text) => { const s = String(text || '').trim(); if (!s) return { main: '', tail: '' }; const m1 = s.match(/^(.+?)[((]\s*未搜索到答案\s*[))]\s*$/); if (m1) return { main: m1[1].trim(), tail: '(未搜索到答案)' }; const m2 = s.match(/^(.+?)未搜索到答案\s*$/); if (m2) return { main: m2[1].replace(/[((]\s*$/, '').trim(), tail: '(未搜索到答案)' }; if (/未找到答案,可点击/.test(s)) { const idx = s.indexOf('未找到答案'); return { main: s.slice(0, idx).replace(/[::]\s*$/, '').trim(), tail: s.slice(idx).trim() }; } return { main: s, tail: '' }; }, ctFormatWorkSummary = (text, kind) => { const colors = { submit: CT_LOG.ok, save: 'blue', manual: CT_LOG.muted, error: CT_LOG.fail }; return ctSpan(colors[kind] || CT_LOG.question, text); }; for (let i = 0; i < questionNum; i++) { let questionElement = questionsElement[i], idElements = questionElement.getElementsByTagName('input'), questionId = '0', question = questionElement.getElementsByClassName('Py-m1-title fs16')[0].innerHTML; question = handleImgs(question).replace(/(<([^>]+)>)/ig, '').replace(/[0-9]{1,3}.\[(.*?)\]/ig, '').replaceAll('\n', '').replace(/^\s+/ig, '').replace(/\s+$/ig, ''); for (let z = 0, k = idElements.length; z < k; z++) { try { if (idElements[z].getAttribute('name').indexOf('answer') >= 0) { questionId = idElements[z].getAttribute('name').replace('type', ''); break; } } catch (e) { console.log(e); continue; } } if (questionId == '0' || question == '') { continue; } typeE = questionElement.getElementsByTagName('input'); if (typeE == null || typeE == []) { continue; } let typeN = 'fuckme'; for (let g = 0, h = typeE.length; g < h; g++) { if (typeE[g].id == 'answertype' + questionId.replace('answer', '').replace('check', '')) { typeN = typeE[g].value; break; } } if (['0', '1', '3'].indexOf(typeN) < 0) { continue; } type = { '0': '单选题', '1': '多选题', '3': '判断题' }[typeN]; let optionList = { length: 0 }; if (['单选题', '多选题'].indexOf(type) >= 0) { let answersElements = questionElement.getElementsByClassName('answerList')[0].getElementsByTagName( 'li'); for (let x = 0, j = answersElements.length; x < j; x++) { let optionE = answersElements[x], optionTextE = trim(optionE.innerHTML.replace(/(^\s*)|(\s*$)/g, "")), optionText = optionTextE.slice(1).replace(/(^\s*)|(\s*$)/g, ""), optionValue = optionTextE.slice(0, 1), optionId = optionE.getAttribute('id-param'); if (optionText == '') { break; } optionList[optionText] = { 'id': optionId, 'value': optionValue } optionList.length++; } if (answersElements.length != optionList.length) { continue; } } questionList.push({ 'question': question, 'type': type, 'questionid': questionId, 'options': optionList }); } totalQuestionNum = questionList.length; var qu = null, nowTime = -4000, busyThread = questionList.length, ctOnload = function (res, quu, index, total) { busyThread -= 1; const formatQuestionDisplay = function (qu, ans, idx, tot, skipClick, pageLabel, selectedOption, syncedFrom, humorLine) { const headerHtml = ctSpan(CT_LOG.header, '【第' + idx + '/' + tot + '题 - ' + qu['type'] + '】'); const questionHtml = ctSpan(CT_LOG.question, '📋 ' + qu['question']); let answerLine = ''; let answerColor = CT_LOG.fail; if (skipClick) { const notePart = '(与搜题结果一致,跳过点击)'; if (pageLabel) { return headerHtml + '
' + questionHtml + '
' + ctSpan(CT_LOG.ok, '✅ 已作答:' + pageLabel) + ctSpan(CT_LOG.ok, notePart); } return headerHtml + '
' + questionHtml + '
' + ctSpan(CT_LOG.ok, '✅ 已作答') + ctSpan(CT_LOG.ok, notePart); } else if (ans) { const ansStr = String(ans); if (syncedFrom) { const toLabel = selectedOption || pageLabel || ansStr.trim(); const mainPart = '✅ 答案:' + ansStr; const notePart = '(原选「' + syncedFrom + '」,搜题为「' + toLabel + '」,已同步)'; return headerHtml + '
' + questionHtml + '
' + ctSpan(CT_LOG.ok, mainPart) + ctSpan(CT_LOG.syncNote, notePart); } if (/(未能填入选项)$/.test(ansStr)) { const mainAns = ansStr.replace(/(未能填入选项)$/, ''); return headerHtml + '
' + questionHtml + '
' + ctSpan(CT_LOG.ok, '✅ 答案:' + mainAns) + ctSpan(CT_LOG.warn, '(未能填入选项)'); } if (/^未找到,自动/.test(ansStr)) { answerLine = selectedOption ? ('❗ 自动选答:' + ansStr + ' 选' + selectedOption) : ('❗ 自动选答:' + ansStr); answerColor = CT_LOG.random; } else { answerLine = selectedOption ? ('✅ 答案:' + ansStr + ' 选' + selectedOption) : ('✅ 答案:' + ansStr); answerColor = CT_LOG.ok; } } else if (humorLine) { const humorParts = ctSplitHumorNoAnswerLine(humorLine); let humorHtml = ctSpan(CT_LOG.ok, humorParts.main); if (humorParts.tail) humorHtml += ctSpan(CT_LOG.fail, humorParts.tail); return headerHtml + '
' + questionHtml + '
' + humorHtml; } else { answerLine = '❌ 未找到答案'; answerColor = CT_LOG.fail; } return headerHtml + '
' + questionHtml + '
' + ctSpan(answerColor, answerLine); }; var ctResult = { 'code': -1, 'finalUrl': '', 'data': '未找到答案,可点击蓝色复制题目按钮后手动自行搜索处理' }; if (res && res.responseText) { try { ctResult = JSON.parse(res.responseText); } catch (e) { console.log(e); } } try { let choiceEs = _d.getElementsByTagName('li'); if (ctResult['code'] == -1) { try { if (ctResult['msg'] !== undefined) { _w.top.unrivalWorkInfo = ctSpan(CT_LOG.fail, String(ctResult['msg'])); } } catch (e) { } } var result = ''; let humorLine = ''; try { if (ctResult['code'] != -1 && ctResult['data'] != null && ctResult['data'] !== undefined) { result = ctResult['data']; } const _rawAns = String(result || '').trim(); if (/\(未搜索到答案\)|未搜索到答案|未找到答案,可点击/.test(_rawAns)) { if (_rawAns) humorLine = _rawAns; result = ''; } } catch (e) { _w.top.unrivalWorkInfo = formatQuestionDisplay(quu, '', index, total, false, '', '', '', humorLine); return; } switch (quu['type']) { case '判断题': (function () { const applied = ctApplyIcodefAnswer(quu, result, choiceEs); if (applied.hit) { foundCount += 1; _w.top.unrivalWorkInfo = formatQuestionDisplay(quu, result, index, total, applied.skipClick, applied.pageLabel, applied.selectedOption, applied.syncedFrom); return; } if (ctHasWorkAnswer(result)) { _w.top.unrivalWorkInfo = formatQuestionDisplay(quu, result + '(未能填入选项)', index, total, false, '', ''); return; } if ((randomDo == 1 || _w.top.unrivalRandomDo == 1) && accuracy < 100) { for (let u = 0, k = choiceEs.length; u < k; u++) { if (choiceEs[u].getElementsByTagName('em').length < 1) { continue; } if (choiceEs[u].getAttribute('val-param') == 'false' && choiceEs[u].getAttribute('id-param') == ctWorkQid(quu)) { if (!choiceEs[u].getAttribute('class') || !choiceEs[u].getAttribute('class').includes('cur')) { choiceEs[u].click(); } const emEl = choiceEs[u].getElementsByTagName('em')[0]; let selectedOption = ''; if (emEl) { selectedOption = emEl.getAttribute('id-param') || emEl.textContent || ''; } _w.top.unrivalWorkInfo = formatQuestionDisplay(quu, '未找到,自动选【错】', index, total, false, '', selectedOption); return; } } } _w.top.unrivalWorkInfo = formatQuestionDisplay(quu, '', index, total, false, '', '', '', humorLine); })(); break; case '单选题': (function () { const applied = ctApplyIcodefAnswer(quu, result, choiceEs); if (applied.hit) { foundCount += 1; _w.top.unrivalWorkInfo = formatQuestionDisplay(quu, result, index, total, applied.skipClick, applied.pageLabel, applied.selectedOption, applied.syncedFrom); return; } if (ctHasWorkAnswer(result)) { _w.top.unrivalWorkInfo = formatQuestionDisplay(quu, result + '(未能填入选项)', index, total, false, '', ''); return; } if ((randomDo == 1 || _w.top.unrivalRandomDo == 1) && accuracy < 100) { for (let y = 0, j = choiceEs.length; y < j; y++) { if (choiceEs[y].getElementsByTagName('em').length < 1) { continue; } if (choiceEs[y].getElementsByTagName('em')[0].getAttribute('id-param') == 'C' && choiceEs[y].getAttribute('id-param') == ctWorkQid(quu)) { if (!choiceEs[y].getAttribute('class').includes('cur')) { choiceEs[y].click(); } const emEl = choiceEs[y].getElementsByTagName('em')[0]; let selectedOption = ''; if (emEl) { selectedOption = emEl.getAttribute('id-param') || emEl.textContent || ''; } _w.top.unrivalWorkInfo = formatQuestionDisplay(quu, '未找到,自动选【C】', index, total, false, '', selectedOption); return; } } } _w.top.unrivalWorkInfo = formatQuestionDisplay(quu, '', index, total, false, '', '', '', humorLine); })(); break; case '多选题': (function () { const applied = ctApplyIcodefAnswer(quu, result, choiceEs); if (applied.hit) { foundCount += 1; _w.top.unrivalWorkInfo = formatQuestionDisplay(quu, result, index, total, applied.skipClick, applied.pageLabel, applied.selectedOption, applied.syncedFrom); return; } if (ctHasWorkAnswer(result)) { _w.top.unrivalWorkInfo = formatQuestionDisplay(quu, result + '(未能填入选项)', index, total, false, '', ''); return; } if ((randomDo == 1 || _w.top.unrivalRandomDo == 1) && accuracy < 100) { let selectedOptions = []; for (let y = 0, j = choiceEs.length; y < j; y++) { if (choiceEs[y].getElementsByTagName('em').length < 1) { continue; } if (choiceEs[y].getAttribute('id-param') == ctWorkQid(quu)) { if (!choiceEs[y].getAttribute('class').includes('cur')) { choiceEs[y].click(); const emEl = choiceEs[y].getElementsByTagName('em')[0]; let opt = ''; if (emEl) { opt = emEl.getAttribute('id-param') || emEl.textContent || ''; } if (opt) selectedOptions.push(opt); } } } const selectedStr = selectedOptions.length > 0 ? selectedOptions.join('') : ctGetSelectedLetters(quu, choiceEs) || '全选'; _w.top.unrivalWorkInfo = formatQuestionDisplay(quu, '未找到,自动全选', index, total, false, '', selectedStr); return; } _w.top.unrivalWorkInfo = formatQuestionDisplay(quu, '', index, total, false, '', '', '', humorLine); })(); break; } } catch (e) { console.log(e); } } for (let i = 0, l = questionList.length; i < l; i++) { nowTime += parseInt(Math.random() * 2000 + 2500, 10); setTimeout(function () { qu = questionList[i]; const searchPayload = ctBuildSearchPayload(qu); let param = 'question=' + encodeURIComponent(searchPayload.question); if (ctUrl.includes('icodef')) { param += '&type=' + { '单选题': '0', '多选题': '1', '判断题': '3' }[qu['type']] + '&id=' + wid; } gmRequest({ method: "POST", headers: { 'Content-type': 'application/x-www-form-urlencoded', 'Authorization': token, }, url: ctUrl, timeout: 2000, data: param, onload: function (res) { ctOnload(res, qu, i + 1, l); }, onerror: function () { ctOnload(false, qu, i + 1, l); }, ontimeout: function () { ctOnload(false, qu, i + 1, l); } }); }, nowTime); } var workInterval = setInterval(function () { if (busyThread != 0) { return; } clearInterval(workInterval); const hitRate = totalQuestionNum > 0 ? Math.floor((foundCount / totalQuestionNum) * 100) : 0; if (hitRate >= accuracy && _w.top.unrivalAutoSubmit == '1') { _w.top.unrivalDoneWorkId = getQueryVariable('workId'); _w.top.unrivalWorkInfo = ctFormatWorkSummary( '搜题命中率 ' + hitRate + '%(命中 ' + foundCount + '/' + totalQuestionNum + ')达到正确率,已提交答案', 'submit'); setTimeout(function () { submitCheckTimes(); escapeBlank() submitAction() // setTimeout(function() { // document.querySelector(".cx_alert-blue").click() // }, parseInt(1000)); }, parseInt(Math.random() * 2000 + 3000, 10)); } else if (_w.top.unrivalAutoSave == 1) { _w.top.unrivalWorkInfo = ctFormatWorkSummary( '搜题命中率 ' + hitRate + '%(命中 ' + foundCount + '/' + totalQuestionNum + ')未达到正确率或未开自动提交,已自动保存答案', 'save'); if (totalQuestionNum > 0) { setTimeout(function () { xxtHookSaveSuccessAlert(xxtOnWorkSaved); _w.top.unrivalDoneWorkId = getQueryVariable('workId'); _w.noSubmit(); }, 2000); } } else { _w.top.unrivalWorkInfo = ctFormatWorkSummary('用户设置为不自动保存答案,请手动提交或保存作业', 'manual'); } }, 1000); } else if (_l.href.includes('work/phone/selectWorkQuestionYiPiYue')) { _w.top.unrivalWorkDone = true; _w.top.unrivalDoneWorkId = getQueryVariable('workId'); } else if (_l.href.includes('stat2-ans.chaoxing.com/task/s/index')) { if (_w.top == _w) { return; } _d.getElementsByClassName('page-container studentStatistic')[0].setAttribute('class', 'studentStatistic'); _d.getElementsByClassName('page-item item-task-list minHeight390')[0].remove(); _d.getElementsByClassName('subNav clearfix')[0].remove(); setInterval(function () { _l.reload(); }, 90000); } else if (_l.href.includes('passport2.') && _l.href.includes('login?refer=http') && autoLogin == 1) { if (!(/^1[3456789]\d{9}$/.test(phoneNumber))) { alert('自动登录的手机号填写错误,无法登陆') return; } if (password == '') { alert('未填写登录密码,无法登陆') return; } gmRequest({ method: "get", url: 'https://passport2-api.chaoxing.com/v11/loginregister?cx_xxt_passport=json&uname=' + phoneNumber + '&code=' + encodeURIComponent(password), onload: function (res) { try { let ispass = JSON.parse(res.responseText); if (ispass['status']) { _l.href = decodeURIComponent(getQueryVariable('refer')); } else { alert(ispass['mes']); } } catch (err) { console.log(res.responseText); alert('登陆失败'); } }, onerror: function (err) { alert('登陆错误'); } }); } // 初始化完成日志g('脚本已加载 v' + GM_info.script.version); // 使用须知弹窗功能 function showUsageGuide() { // 检查是否为课程列表页面(只在课程列表页面显示弹窗,不在单个课程的章节目录页面显示) const isCatalogPage = () => { const href = _l.href; const pathname = _l.pathname; // 排除学习页面和单个课程的章节目录页面 if (href.includes('studentstudy') || href.includes('knowledge/cards')) { return false; } // 排除单个课程的章节目录页面(如:/mooc-ans/course/233047813.html) if (/\/mooc-ans\/course\/\d+/.test(pathname) || /\/mooc2-ans\/course\/\d+/.test(pathname)) { return false; } // 只匹配真正的课程列表页面URL const isMatch = href.includes('mycourse/studentcourse') || href.includes('mycourse/stu'); return isMatch; }; if (!isCatalogPage()) { return; } // 检查今日是否已禁用 const getTodayDateString = () => { const today = new Date(); const year = today.getFullYear(); const month = String(today.getMonth() + 1).padStart(2, '0'); const day = String(today.getDate()).padStart(2, '0'); return `${year}-${month}-${day}`; }; const dismissedDate = GM_getValue('usage_guide_dismissed_date', ''); const todayDate = getTodayDateString(); if (dismissedDate === todayDate) { return; } // 双开脚本时允许多层;每层独立绑定,避免全局 id + getElementById 绑错层 const guideStack = document.querySelectorAll('.xxt-usage-guide-overlay').length; const guideZBase = 2147483646; const overlay = document.createElement('div'); overlay.className = 'xxt-usage-guide-overlay'; overlay.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.3); z-index: ${guideZBase + guideStack * 2}; display: flex; align-items: center; justify-content: center; pointer-events: auto; `; const modal = document.createElement('div'); modal.className = 'xxt-usage-guide-modal'; modal.style.cssText = ` position: relative; width: 640px; max-width: 92vw; background: white; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.15); z-index: ${guideZBase + guideStack * 2 + 1}; font-family: 'Microsoft YaHei', Arial, sans-serif; border: 1px solid #e0e0e0; pointer-events: auto; `; modal.innerHTML = ` `; overlay.appendChild(modal); document.body.appendChild(overlay); const removeUsageGuide = () => { try { if (overlay.parentNode) overlay.remove(); } catch (_) { } document.removeEventListener('keydown', onUsageGuideEsc); }; const onUsageGuideEsc = (e) => { if (e.key === 'Escape' && overlay.parentNode) { removeUsageGuide(); } }; const closeBtn = overlay.querySelector('.xxt-usage-guide-close'); const dismissBtn = overlay.querySelector('.xxt-usage-guide-dismiss'); if (closeBtn) { closeBtn.addEventListener('click', removeUsageGuide); } if (dismissBtn) { dismissBtn.addEventListener('click', () => { GM_setValue('usage_guide_dismissed_date', todayDate); removeUsageGuide(); }); } document.addEventListener('keydown', onUsageGuideEsc); } setTimeout(showUsageGuide, 500); })(); // ==================== 阅读助手功能开始 ==================== (function() { 'use strict'; function isReadingTaskPage() { if (location.href.includes('/mooc-ans/zt/portal/')) return true; if (location.href.includes('/mooc-ans/course/') && location.href.includes('.html')) return true; try { return /\/course\/\d+\.html/i.test(new URL(location.href, location.origin).pathname); } catch (e) { return /\/course\/\d+\.html/i.test(location.pathname); } } function isReadingPage() { return location.href.includes('/ztnodedetailcontroller/visitnodedetail'); } if (!isReadingTaskPage() && !isReadingPage()) { return; } // 全局状态管理(跨页面保持) window.readingAssistantGlobalState = window.readingAssistantGlobalState || { isRunning: false, isPaused: false, scrollPosition: 0, manuallyPaused: false, startTime: 0, chapterElapsed: 0, timerInterval: null }; // 防重复跳转锁 let hasAutoJump = false; // 配置 const BEAR_URL = { run: 'https://s1.aigei.com/src/img/gif/33/332524b9bbe34f029fc0f2744ebcfd27.gif?e=2051020800&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:zufjktQMAvjjC6hhOdGSlI_oXsY=', eat: 'https://s1.aigei.com/src/img/gif/ad/adc5c174b4a94e93b67702a76adb63d4.gif?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=2051020800&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:JhiMrZ_DLfOiBqibRnWMMHtUN6U=' }; const REWARD_QR = 'data:image/jpeg;base64,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'; const CONFIG = { scrollSpeed: parseFloat(GM_getValue('scrollSpeed', 1.0)), scrollMode: GM_getValue('scrollMode', 'paragraph'), // paragraph, page, pixel scrollPixel: parseInt(GM_getValue('scrollPixel', 300)), scrollJitter: GM_getValue('scrollJitter', true) !== false, scrollIntervalJitter: GM_getValue('scrollIntervalJitter', true) !== false, randomPauseEnabled: GM_getValue('randomPauseEnabled', true) !== false, randomPauseEveryMin: parseInt(GM_getValue('randomPauseEveryMin', 3), 10) || 3, randomPauseEveryMax: parseInt(GM_getValue('randomPauseEveryMax', 8), 10) || 8, randomPauseMsMin: parseInt(GM_getValue('randomPauseMsMin', 1000), 10) || 1000, randomPauseMsMax: parseInt(GM_getValue('randomPauseMsMax', 5000), 10) || 5000, autoStart: GM_getValue('autoStart', true), showTips: GM_getValue('showTips', true), showFloatBear: GM_getValue('showFloatBear', true) !== false, bearIconSize: parseInt(GM_getValue('bearIconSize', 77), 10) || 77, highlightMode: GM_getValue('highlightMode', true) !== false, loopMode: GM_getValue('loopMode', true), // 循环阅读模式 targetMinutes: parseInt(GM_getValue('readingTargetMinutes', 0), 10) || 0, showChapterTimer: GM_getValue('readingShowChapterTimer', true) !== false }; const TASK_ELAPSED_PREFIX = 'readingTaskSec_'; const GOAL_NOTIFIED_PREFIX = 'readingGoalDone_'; const SESSION_TASK_KEY = 'readingAssistantTabTaskKey'; let resolvedReadingTaskKey = null; // 状态 const STATE = { get isRunning() { return window.readingAssistantGlobalState.isRunning; }, set isRunning(value) { window.readingAssistantGlobalState.isRunning = value; }, get isPaused() { return window.readingAssistantGlobalState.isPaused; }, set isPaused(value) { window.readingAssistantGlobalState.isPaused = value; }, get currentScrollTop() { return window.readingAssistantGlobalState.scrollPosition; }, set currentScrollTop(value) { window.readingAssistantGlobalState.scrollPosition = value; }, get manuallyPaused() { return window.readingAssistantGlobalState.manuallyPaused; }, set manuallyPaused(value) { window.readingAssistantGlobalState.manuallyPaused = value; }, contentElements: [], currentIndex: 0, scrollTimer: null, pixelScrollCount: 0, nextPauseAt: 0 }; let bearClickTimer = null; let bearLongPressTimer = null; let bearLongPressFired = false; let catalogJumpObserver = null; let catalogJumpTimeout = null; const CONTENT_ROOT_SELECTORS = ['.read_content', '.article', '.content', '#content', '.node-content', '.chapter-content', '.ans-cc', 'main']; const CONTENT_EXCLUDE_SELECTOR = '#reading-bear-float,#reading-tips,#reading-settings-modal,#reading-toast,.ztop,.zt_center,.zt_user,.zt_u_abs,header,nav,.header,.nav'; // 日志 function log(msg) { console.log(`[学习通阅读助手] ${msg}`); } // 格式化时间(秒 → 00:00:00) function formatTime(seconds) { const h = String(Math.floor(seconds / 3600)).padStart(2, '0'); const m = String(Math.floor((seconds % 3600) / 60)).padStart(2, '0'); const s = String(seconds % 60).padStart(2, '0'); return `${h}:${m}:${s}`; } // 解析 URL 参数(键名小写) function parseUrlParams(url) { try { const u = new URL(url, location.origin); const p = {}; u.searchParams.forEach(function (v, k) { p[k.toLowerCase()] = v; }); return p; } catch (e) { return {}; } } function sanitizeTaskKeyPart(s) { return String(s || '').replace(/[^\w.-]/g, '_').slice(0, 64); } function readTabTaskKeyHint() { try { return sessionStorage.getItem(SESSION_TASK_KEY) || ''; } catch (e) { return ''; } } function writeTabTaskKeyHint(key) { try { if (key) sessionStorage.setItem(SESSION_TASK_KEY, key); } catch (e) {} } // 阅读任务目录页 → 任务键(含 query,区分同课多任务) function computeCatalogTaskKey(url) { url = url || location.href; let path; try { path = new URL(url, location.origin).pathname; } catch (e) { path = location.pathname; } const m = path.match(/\/course\/(\d+)\.html/i); const params = parseUrlParams(url); if (m) { const qIdx = url.indexOf('?'); const q = qIdx >= 0 ? url.slice(qIdx + 1, qIdx + 81) : ''; return 'rd_c' + m[1] + (q ? '_' + sanitizeTaskKeyPart(q) : ''); } if (path.indexOf('/zt/portal') !== -1) { return 'rd_p' + sanitizeTaskKeyPart(params.id || params.courseid || params.jobid || path.replace(/\W/g, '_')); } const qIdx = url.indexOf('?'); const qs = qIdx >= 0 ? url.slice(qIdx + 1, qIdx + 81) : 'x'; return 'rd_' + sanitizeTaskKeyPart(path.replace(/\W/g, '_')) + '_' + sanitizeTaskKeyPart(qs); } function isReadingChapterFlip() { const ref = document.referrer || ''; return ref.indexOf('/ztnodedetailcontroller/visitnodedetail') !== -1; } // 阅读正文页 → 从 URL 算任务键(仅 cardid/cfid,不含章节级参数) function computeReadingPageTaskKey(url) { url = url || location.href; const p = parseUrlParams(url); const cid = p.courseid || p.coursid || p.clazzid || ''; const taskPart = p.cardid || p.cfid || ''; if (cid && taskPart) { return 'rd_' + sanitizeTaskKeyPart(cid) + '_' + sanitizeTaskKeyPart(taskPart); } if (taskPart) { return 'rd_t_' + sanitizeTaskKeyPart(taskPart); } return ''; } function computeReadingPageWeakKey(url) { url = url || location.href; const qIdx = url.indexOf('?'); const qs = qIdx >= 0 ? url.slice(qIdx + 1) : ''; if (qs) { return 'rd_q_' + sanitizeTaskKeyPart(qs.slice(0, 120)); } try { return 'rd_path_' + sanitizeTaskKeyPart(new URL(url, location.origin).pathname.replace(/\W/g, '_')); } catch (e) { return ''; } } function resolveReadingPageTaskKey() { const tabHint = readTabTaskKeyHint(); // 同任务内翻章:沿用本标签已确定的任务键,避免章节 URL 变化导致计时归零 if (tabHint && isReadingChapterFlip()) { return tabHint; } const fromUrl = computeReadingPageTaskKey(location.href); if (fromUrl) return fromUrl; if (tabHint) return tabHint; const ref = document.referrer || ''; if (ref && (ref.indexOf('/mooc-ans/course/') !== -1 || ref.indexOf('/zt/portal') !== -1)) { return computeCatalogTaskKey(ref); } const weak = computeReadingPageWeakKey(location.href); if (weak) return weak; return 'rd_unknown_' + sanitizeTaskKeyPart(location.href.slice(-120)); } // 当前阅读任务键(按任务隔离;阅读页仅用本页 URL / 本标签 sessionStorage) function getReadingTaskKey() { if (resolvedReadingTaskKey) return resolvedReadingTaskKey; if (isReadingTaskPage()) { resolvedReadingTaskKey = computeCatalogTaskKey(location.href); writeTabTaskKeyHint(resolvedReadingTaskKey); return resolvedReadingTaskKey; } if (isReadingPage()) { resolvedReadingTaskKey = resolveReadingPageTaskKey(); writeTabTaskKeyHint(resolvedReadingTaskKey); return resolvedReadingTaskKey; } resolvedReadingTaskKey = 'rd_unknown'; return resolvedReadingTaskKey; } function getTaskSavedElapsed() { return GM_getValue(TASK_ELAPSED_PREFIX + getReadingTaskKey(), 0); } function saveTaskSavedElapsed(seconds) { GM_setValue(TASK_ELAPSED_PREFIX + getReadingTaskKey(), Math.max(0, Math.floor(seconds))); } function getTaskDisplayElapsed() { return getTaskSavedElapsed() + (window.readingAssistantGlobalState.chapterElapsed || 0); } function formatTarget(minutes) { if (!minutes || minutes <= 0) return '无'; return formatTime(minutes * 60); } function getGoalStatus() { const targetMin = CONFIG.targetMinutes; if (!targetMin || targetMin <= 0) return 'none'; return getTaskDisplayElapsed() >= targetMin * 60 ? 'done' : 'pending'; } function renderGoalBadge() { const status = getGoalStatus(); if (status === 'none') return ''; if (status === 'done') { return ' ✓ 已达标'; } const pct = Math.min(100, Math.floor(getTaskDisplayElapsed() / (CONFIG.targetMinutes * 60) * 100)); return ' ✗ 未达标 ' + pct + '%'; } function ensureReadingTipsStyle() { if (document.getElementById('reading-tips-style')) return; const s = document.createElement('style'); s.id = 'reading-tips-style'; s.textContent = [ '.reading-goal-badge{display:inline-block;margin-left:6px;padding:2px 8px;border-radius:10px;font-size:11px;font-weight:600;vertical-align:middle;}', '.reading-goal-pending{background:rgba(244,67,54,0.22);color:#ffb4ab;}', '.reading-goal-ok{background:rgba(76,175,80,0.28);color:#b9f6ca;}', '.reading-goal-pop{animation:readingGoalPop 0.55s ease;}', '@keyframes readingGoalPop{0%{transform:scale(1)}45%{transform:scale(1.12)}100%{transform:scale(1)}}' ].join(''); document.head.appendChild(s); } function showReadingToast(msg, durationMs) { durationMs = durationMs || 5000; let el = document.getElementById('reading-toast'); if (el) el.remove(); el = document.createElement('div'); el.id = 'reading-toast'; el.textContent = msg; el.style.cssText = 'position:fixed;top:20px;left:50%;transform:translateX(-50%);' + 'background:rgba(40,40,40,0.92);color:#fff;padding:12px 20px;border-radius:8px;' + 'font-size:14px;z-index:10000;max-width:90%;text-align:center;line-height:1.5;'; document.body.appendChild(el); setTimeout(function () { if (el.parentNode) el.remove(); }, durationMs); } function checkReadingGoal() { if (getGoalStatus() !== 'done') return; const notifyKey = GOAL_NOTIFIED_PREFIX + getReadingTaskKey(); if (GM_getValue(notifyKey, false)) return; GM_setValue(notifyKey, true); log('本任务已达目标时长 ' + CONFIG.targetMinutes + ' 分钟'); const badge = document.getElementById('reading-goal-badge'); if (badge) badge.classList.add('reading-goal-pop'); } // 本章时长累加到本任务 function flushChapterToTask() { const ch = window.readingAssistantGlobalState.chapterElapsed || 0; if (ch > 0) { saveTaskSavedElapsed(getTaskSavedElapsed() + ch); window.readingAssistantGlobalState.chapterElapsed = 0; window.readingAssistantGlobalState.startTime = 0; } } // 启动计时(当前章) function startTimer() { if (window.readingAssistantGlobalState.timerInterval) return; window.readingAssistantGlobalState.startTime = Date.now() - window.readingAssistantGlobalState.chapterElapsed * 1000; window.readingAssistantGlobalState.timerInterval = setInterval(function () { window.readingAssistantGlobalState.chapterElapsed = Math.floor((Date.now() - window.readingAssistantGlobalState.startTime) / 1000); updateTips(); }, 1000); } // 停止计时 function stopTimer() { if (window.readingAssistantGlobalState.timerInterval) { clearInterval(window.readingAssistantGlobalState.timerInterval); window.readingAssistantGlobalState.timerInterval = null; } } // 重置当前章时长 function resetChapterTimer() { stopTimer(); window.readingAssistantGlobalState.chapterElapsed = 0; window.readingAssistantGlobalState.startTime = 0; } // R 键:重置当前阅读任务计时 function resetTaskTimer() { const taskKey = getReadingTaskKey(); saveTaskSavedElapsed(0); GM_setValue(GOAL_NOTIFIED_PREFIX + taskKey, false); resetChapterTimer(); updateTips(); log('本任务阅读时长已重置'); showReadingToast('本任务阅读时长已重置'); } // 更新提示栏(本任务 + 目标 + 本章) function updateTips() { const tips = document.getElementById('reading-tips'); if (!tips) return; const taskSec = getTaskDisplayElapsed(); const chapter = window.readingAssistantGlobalState.chapterElapsed; let line2 = '本任务 ' + formatTime(taskSec) + ' / ' + formatTarget(CONFIG.targetMinutes); if (CONFIG.showChapterTimer) { line2 += ' · 本章 ' + formatTime(chapter); } line2 += renderGoalBadge(); tips.innerHTML = 'K开始 Z暂停 S设置 R重置 · 点熊切换/双击设置/长按重置
' + line2; checkReadingGoal(); updateBearIcon(); } function randomBetween(min, max) { const lo = Math.min(min, max); const hi = Math.max(min, max); return lo + Math.floor(Math.random() * (hi - lo + 1)); } function getRandomPixelStep() { const base = CONFIG.scrollPixel; if (!CONFIG.scrollJitter) return base; return Math.max(30, Math.round(base * (0.7 + Math.random() * 0.6))); } function getRandomIntervalMs(baseMs) { if (!CONFIG.scrollIntervalJitter) return baseMs; return Math.max(200, Math.round(baseMs * (0.75 + Math.random() * 0.5))); } function scheduleNextPauseAt() { const min = Math.min(CONFIG.randomPauseEveryMin, CONFIG.randomPauseEveryMax); const max = Math.max(CONFIG.randomPauseEveryMin, CONFIG.randomPauseEveryMax); STATE.nextPauseAt = randomBetween(min, max); STATE.pixelScrollCount = 0; } function isBearRunningVisual() { return STATE.isRunning && !STATE.isPaused; } function updateBearIcon() { const img = document.getElementById('reading-bear-img'); if (!img) return; img.src = isBearRunningVisual() ? BEAR_URL.run : BEAR_URL.eat; img.title = isBearRunningVisual() ? '阅读中(单击暂停)' : '已停止(单击开始)'; } function toggleReadingByBear() { if (!isReadingPage()) { showReadingToast('请先进入阅读章节,再点熊开始'); return; } if (isBearRunningVisual()) { pauseReading(); } else { resumeReading(); } updateBearIcon(); } function bindBearPointer(el) { const clearLongPress = function () { if (bearLongPressTimer) { clearTimeout(bearLongPressTimer); bearLongPressTimer = null; } }; const startLongPress = function () { clearLongPress(); bearLongPressFired = false; bearLongPressTimer = setTimeout(function () { bearLongPressFired = true; if (bearClickTimer) { clearTimeout(bearClickTimer); bearClickTimer = null; } if (!isReadingPage()) { showReadingToast('请在阅读章节页长按重置'); return; } resetTaskTimer(); updateBearIcon(); }, 600); }; el.addEventListener('mousedown', startLongPress); el.addEventListener('mouseup', clearLongPress); el.addEventListener('mouseleave', clearLongPress); el.addEventListener('touchstart', function (e) { startLongPress(); }, { passive: true }); el.addEventListener('touchend', clearLongPress); el.addEventListener('touchcancel', clearLongPress); el.addEventListener('click', function (e) { e.preventDefault(); e.stopPropagation(); if (bearLongPressFired) { bearLongPressFired = false; return; } if (bearClickTimer) { clearTimeout(bearClickTimer); bearClickTimer = null; showSettings(); return; } bearClickTimer = setTimeout(function () { bearClickTimer = null; toggleReadingByBear(); }, 280); }); } function showFloatBear() { if (!CONFIG.showFloatBear) return; if (!isReadingPage() && !isReadingTaskPage()) return; if (document.getElementById('reading-bear-float')) return; const size = Math.max(40, Math.min(96, CONFIG.bearIconSize)); const wrap = document.createElement('div'); wrap.id = 'reading-bear-float'; wrap.style.cssText = 'position:fixed;left:12px;bottom:12px;z-index:9999;' + 'width:' + size + 'px;height:' + size + 'px;cursor:pointer;user-select:none;' + '-webkit-user-select:none;touch-action:manipulation;'; const img = document.createElement('img'); img.id = 'reading-bear-img'; img.draggable = false; img.style.cssText = 'width:100%;height:100%;display:block;object-fit:contain;pointer-events:none;'; wrap.appendChild(img); document.body.appendChild(wrap); bindBearPointer(wrap); updateBearIcon(); } function disconnectCatalogObserver() { if (catalogJumpObserver) { catalogJumpObserver.disconnect(); catalogJumpObserver = null; } if (catalogJumpTimeout) { clearTimeout(catalogJumpTimeout); catalogJumpTimeout = null; } } function cleanupReadingSession() { clearTimeout(STATE.scrollTimer); STATE.scrollTimer = null; stopTimer(); flushChapterToTask(); disconnectCatalogObserver(); if (bearClickTimer) { clearTimeout(bearClickTimer); bearClickTimer = null; } if (bearLongPressTimer) { clearTimeout(bearLongPressTimer); bearLongPressTimer = null; } } function bindPageLifecycle() { if (window.readingAssistantPageLifecycleBound) return; window.addEventListener('pagehide', cleanupReadingSession); window.readingAssistantPageLifecycleBound = true; } function getContentSearchRoot() { for (let i = 0; i < CONTENT_ROOT_SELECTORS.length; i++) { const el = document.querySelector(CONTENT_ROOT_SELECTORS[i]); if (el && el.querySelector('p, h1, h2, h3, h4, h5, h6')) return el; } return document.body; } function isExcludedContentElement(el) { if (!el || !el.closest) return true; if (el.id === 'reading-bear-img') return true; try { if (el.closest(CONTENT_EXCLUDE_SELECTOR)) return true; } catch (_) { } return false; } // 自动跳转到阅读页面 function autoJumpToReading() { if (hasAutoJump) return; if (!isReadingTaskPage()) return; getReadingTaskKey(); log('检测到阅读任务目录页面,准备跳转'); disconnectCatalogObserver(); catalogJumpObserver = new MutationObserver(function () { const readingLink = document.querySelector( 'a[href*="/ztnodedetailcontroller/visitnodedetail"],.catalog_detail a,.chapter_item a,.nodeItem a,.posCatalog_name a' ); //感谢用户:ᦸᐝSᴗhi꧂的反馈 if (readingLink) { log('找到阅读章节,正在跳转'); readingLink.click(); hasAutoJump = true; disconnectCatalogObserver(); } }); catalogJumpObserver.observe(document.body, { childList: true, subtree: true }); catalogJumpTimeout = setTimeout(function () { disconnectCatalogObserver(); log('目录页等待阅读链接超时,已停止监听'); }, 60000); } // 收集内容元素 function collectContent() { const root = getContentSearchRoot(); const selectors = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'img', 'video']; STATE.contentElements = []; selectors.forEach(function (selector) { root.querySelectorAll(selector).forEach(function (el) { if (isExcludedContentElement(el)) return; if (el.offsetHeight <= 20 || el.offsetWidth <= 20) return; if (selector === 'p') { const text = String(el.textContent || '').trim(); if (text.length < 4) return; } STATE.contentElements.push(el); }); }); STATE.contentElements.sort(function (a, b) { return a.getBoundingClientRect().top - b.getBoundingClientRect().top; }); log('找到 ' + STATE.contentElements.length + ' 个内容元素'); } // 清除高亮 function clearHighlight() { document.querySelectorAll('.reading-highlight').forEach(el => { el.classList.remove('reading-highlight'); el.style.outline = ''; }); } // 段落阅读 function scrollToNext() { if (STATE.isPaused || STATE.currentIndex >= STATE.contentElements.length) { completeReading(); return; } const element = STATE.contentElements[STATE.currentIndex]; if (CONFIG.highlightMode) { clearHighlight(); element.classList.add('reading-highlight'); element.style.outline = '4px solid #00FF00'; element.style.transition = 'outline 0.3s ease'; } element.scrollIntoView({ behavior: 'smooth', block: 'center' }); STATE.currentIndex++; const baseMs = CONFIG.scrollSpeed * 1000; STATE.scrollTimer = setTimeout(scrollToNext, getRandomIntervalMs(baseMs)); } // 整页阅读 function pageScroll() { const totalHeight = document.documentElement.scrollHeight - window.innerHeight - 50; const scrollStep = totalHeight / (CONFIG.scrollSpeed * 10); if (!STATE.currentScrollTop) { STATE.currentScrollTop = 0; } const scroll = () => { if (STATE.isPaused) return; STATE.currentScrollTop += scrollStep; if (STATE.currentScrollTop >= totalHeight) { completeReading(); } else { window.scrollTo({ top: STATE.currentScrollTop, behavior: 'smooth' }); STATE.scrollTimer = setTimeout(scroll, getRandomIntervalMs(100)); } }; scroll(); } // 像素滚动 function pixelScroll() { const totalHeight = document.documentElement.scrollHeight - window.innerHeight - 50; if (!STATE.currentScrollTop) { STATE.currentScrollTop = window.pageYOffset || document.documentElement.scrollTop; } if (!STATE.nextPauseAt) scheduleNextPauseAt(); const scroll = () => { if (STATE.isPaused) return; if (CONFIG.randomPauseEnabled) { STATE.pixelScrollCount += 1; if (STATE.pixelScrollCount >= STATE.nextPauseAt) { scheduleNextPauseAt(); const pauseMs = randomBetween(CONFIG.randomPauseMsMin, CONFIG.randomPauseMsMax); STATE.scrollTimer = setTimeout(scroll, pauseMs); return; } } STATE.currentScrollTop += getRandomPixelStep(); if (STATE.currentScrollTop >= totalHeight) { completeReading(); } else { window.scrollTo({ top: STATE.currentScrollTop, behavior: 'smooth' }); STATE.scrollTimer = setTimeout(scroll, getRandomIntervalMs(CONFIG.scrollSpeed * 1000)); } }; scroll(); } // 完成阅读 function completeReading() { STATE.isRunning = false; STATE.isPaused = false; clearHighlight(); clearTimeout(STATE.scrollTimer); flushChapterToTask(); resetChapterTimer(); updateBearIcon(); log('阅读完成'); setTimeout(function () { const nextSelectors = [ '.nodeItem.r a', '.nodeItem.r i', 'a[title="下一章"]', 'a[title="下一节"]', '.next_btn', '.nextBtn', 'a.next', '.pager_next a', '.next_chapter a' ]; let nextBtn = null; for (let i = 0; i < nextSelectors.length; i++) { nextBtn = document.querySelector(nextSelectors[i]); if (nextBtn) break; } if (nextBtn) { log('找到下一章按钮,正在跳转'); nextBtn.click(); } else if (CONFIG.loopMode) { log('未找到下一章按钮,循环模式开启,准备跳转到第一章'); setTimeout(() => { jumpToFirstChapter(); }, 1000); } else { log('未找到下一章按钮,阅读结束'); } }, 2000); } // 跳转到第一章 function jumpToFirstChapter() { log('开始寻找第一章...'); const firstChapterSelectors = [ '.posCatalog_select:first-child a', '.posCatalog_name:first-child a', '.catalog_points_yi:first-child a', '.catalog_title:first-child a', '.nodeItem:first-child a', '.catalogDetail:first-child a', '.catalog_sectionLevel1:first-child a', 'a[href*="/ztnodedetailcontroller/visitnodedetail"]' ]; let firstChapterLink = null; for (const selector of firstChapterSelectors) { const links = document.querySelectorAll(selector); if (links.length > 0) { firstChapterLink = links[0]; log(`通过选择器 ${selector} 找到第一章链接`); break; } } if (firstChapterLink) { log('找到第一章链接,正在跳转...'); firstChapterLink.click(); return; } log('尝试返回上级页面...'); if (window.history.length > 1) { window.history.back(); } else { const currentUrl = location.href; const urlParts = currentUrl.split('/'); if (urlParts.length > 3) { const rootUrl = urlParts.slice(0, 4).join('/'); log(`跳转到根目录: ${rootUrl}`); location.href = rootUrl; } } } // 开始阅读 function startReading() { if (STATE.scrollTimer) clearTimeout(STATE.scrollTimer); if (STATE.isRunning) return; startTimer(); STATE.isRunning = true; STATE.isPaused = false; STATE.currentIndex = 0; log(`开始${CONFIG.scrollMode}阅读`); updateBearIcon(); switch(CONFIG.scrollMode) { case 'paragraph': collectContent(); if (STATE.contentElements.length > 0) { scrollToNext(); } else { log('未找到段落内容,切换整页模式'); pageScroll(); } break; case 'page': pageScroll(); break; case 'pixel': pixelScroll(); break; } } // 暂停阅读 function pauseReading() { if (STATE.isRunning) { stopTimer(); STATE.isPaused = true; STATE.manuallyPaused = true; GM_setValue('manuallyPaused', true); clearTimeout(STATE.scrollTimer); log('阅读已暂停'); updateBearIcon(); } } // 继续阅读 function resumeReading() { if (!STATE.isRunning) { STATE.manuallyPaused = false; GM_setValue('manuallyPaused', false); startTimer(); startReading(); updateBearIcon(); } else { STATE.isPaused = false; STATE.manuallyPaused = false; GM_setValue('manuallyPaused', false); startTimer(); log('继续阅读'); updateBearIcon(); switch(CONFIG.scrollMode) { case 'paragraph': scrollToNext(); break; case 'page': pageScroll(); break; case 'pixel': pixelScroll(); break; } } } // 停止阅读(供主脚本调用) function stopReading() { STATE.isRunning = false; STATE.isPaused = false; STATE.manuallyPaused = false; clearTimeout(STATE.scrollTimer); clearHighlight(); stopTimer(); flushChapterToTask(); resetChapterTimer(); updateBearIcon(); log('阅读已停止'); } // 显示阅读器设置面板 function showSettings() { const existingModal = document.getElementById('reading-settings-modal'); if (existingModal) { existingModal.remove(); return; } const modal = document.createElement('div'); modal.id = 'reading-settings-modal'; modal.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 20px; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.3); z-index: 9999; font-family: Arial, sans-serif; min-width: 400px; border: 1px solid #ddd; `; modal.innerHTML = `

阅读器设置

仅段落阅读模式
仅像素滚动模式
步长约 70%~130%
仅像素滚动模式
仅像素滚动模式
全部阅读模式
0 = 不提醒
自嘲熊

单击切换
双击设置
长按重置

吃饭饭

小猫 or 小狗

计时按墙钟统计;最小化一般仍继续,自动滚动可能变慢;与学习通平台记录可能不一致
`; document.body.appendChild(modal); document.getElementById('save').onclick = () => { CONFIG.scrollSpeed = parseFloat(document.getElementById('speed').value); CONFIG.scrollMode = document.getElementById('mode').value; CONFIG.scrollPixel = parseInt(document.getElementById('pixel').value); CONFIG.scrollJitter = document.getElementById('scrollJitter').checked; CONFIG.scrollIntervalJitter = document.getElementById('scrollIntervalJitter').checked; CONFIG.randomPauseEnabled = document.getElementById('randomPauseEnabled').checked; CONFIG.bearIconSize = parseInt(document.getElementById('bearIconSize').value, 10) || 77; CONFIG.autoStart = document.getElementById('autoStart').checked; CONFIG.showTips = document.getElementById('showTips').checked; CONFIG.showFloatBear = document.getElementById('showFloatBear').checked; CONFIG.highlightMode = document.getElementById('highlightMode').checked; CONFIG.loopMode = document.getElementById('loopMode').checked; CONFIG.targetMinutes = Math.max(0, parseInt(document.getElementById('targetMinutes').value, 10) || 0); CONFIG.showChapterTimer = document.getElementById('showChapterTimer').checked; GM_setValue('scrollSpeed', CONFIG.scrollSpeed); GM_setValue('scrollMode', CONFIG.scrollMode); GM_setValue('scrollPixel', CONFIG.scrollPixel); GM_setValue('scrollJitter', CONFIG.scrollJitter); GM_setValue('scrollIntervalJitter', CONFIG.scrollIntervalJitter); GM_setValue('randomPauseEnabled', CONFIG.randomPauseEnabled); GM_setValue('bearIconSize', CONFIG.bearIconSize); GM_setValue('autoStart', CONFIG.autoStart); GM_setValue('showTips', CONFIG.showTips); GM_setValue('showFloatBear', CONFIG.showFloatBear); GM_setValue('highlightMode', CONFIG.highlightMode); GM_setValue('loopMode', CONFIG.loopMode); GM_setValue('readingTargetMinutes', CONFIG.targetMinutes); GM_setValue('readingShowChapterTimer', CONFIG.showChapterTimer); if (CONFIG.targetMinutes > 0) { GM_setValue(GOAL_NOTIFIED_PREFIX + getReadingTaskKey(), false); } modal.remove(); log('设置已保存'); const oldBear = document.getElementById('reading-bear-float'); if (oldBear) oldBear.remove(); if (isReadingPage() || isReadingTaskPage()) { const oldTips = document.getElementById('reading-tips'); if (oldTips) oldTips.remove(); showTips(); showFloatBear(); } }; document.getElementById('close').onclick = () => { modal.remove(); }; } // 显示快捷键提示 function showTips() { if (!CONFIG.showTips || !isReadingPage()) return; ensureReadingTipsStyle(); const existingTips = document.getElementById('reading-tips'); if (existingTips) existingTips.remove(); const tips = document.createElement('div'); tips.id = 'reading-tips'; tips.style.cssText = ` position: fixed; bottom: 10px; right: 10px; background: rgba(0,0,0,0.7); color: white; padding: 10px; border-radius: 6px; font-size: 12px; z-index: 9998; font-family: Arial, sans-serif; `; document.body.appendChild(tips); updateTips(); } // 键盘事件 function bindKeys() { if (window.readingAssistantKeysbound) return; document.addEventListener('keydown', (e) => { if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return; switch(e.key.toLowerCase()) { case 'k': e.preventDefault(); resumeReading(); break; case 'z': e.preventDefault(); pauseReading(); break; case 's': e.preventDefault(); showSettings(); break; case 'r': e.preventDefault(); resetTaskTimer(); break; } }); window.readingAssistantKeysbound = true; } // 初始化 function init() { bindPageLifecycle(); bindKeys(); showFloatBear(); if (isReadingTaskPage()) { autoJumpToReading(); } else if (isReadingPage()) { getReadingTaskKey(); if (CONFIG.scrollMode === 'pixel' && window.readingAssistantInitialized) { showTips(); return; } showTips(); const wasManuallyPaused = GM_getValue('manuallyPaused', false); if (CONFIG.autoStart && !wasManuallyPaused) { setTimeout(startReading, 2000); } else if (wasManuallyPaused) { log('检测到手动暂停状态,不自动开始阅读'); } window.readingAssistantInitialized = true; } } // 启动 init(); window.__xxtStopReading = stopReading; })(); // ==================== 阅读助手功能结束 ==================== // ==================== 由衷感谢iPycc ==================== (function() { 'use strict'; // 安全解析 Table 资源,支持回退到 SourceTable const safeParseTable = () => { const tryRead = (key) => { try { const text = GM_getResourceText && GM_getResourceText(key); if (text) return JSON.parse(text); } catch (e) { console.warn(`加载${key}资源失败`, e); } return null; }; return tryRead('Table') || tryRead('SourceTable'); }; const ChaoxingHelper = { // 复制 (字体解密及替换) copyEnabler: { decrypt(retryCount = 0) { // 检查 Typr 和 md5 是否加载 if (typeof Typr === 'undefined' || typeof md5 === 'undefined') { if (retryCount < 5) { setTimeout(() => this.decrypt(retryCount + 1), 500); return; } console.warn('[Chaoxing Helper] Typr 或 md5 未加载,放弃解密'); return; } const styleElement = this.findStyleContaining('font-cxsecret'); if (!styleElement) { if (retryCount < 5) { setTimeout(() => this.decrypt(retryCount + 1), 500); return; } return; } // 支持单引号和双引号 const fontMatch = styleElement.textContent.match(/base64,([\w\W]+?)["']/); if (!fontMatch) return; try { const fontData = Typr.parse(this.base64ToUint8Array(fontMatch[1]))?.[0]; const table = safeParseTable(); if (!fontData || !table) { if (retryCount < 3) { setTimeout(() => this.decrypt(retryCount + 1), 500); return; } return; } const charMap = this.createCharMap(fontData, table); this.replaceEncryptedText(charMap); console.log(`[Chaoxing Helper] Decryption completed ${Object.keys(charMap).length} characters`); } catch (e) { console.warn('[Chaoxing Helper] 解密过程出错:', e); } }, watch() { // 监听样式和字体类的出现,出现后尝试解密 const tryOnce = () => this.decrypt(); const styleObserver = new MutationObserver(() => tryOnce()); const bodyObserver = new MutationObserver(() => tryOnce()); try { styleObserver.observe(document.head || document.documentElement, { childList: true, subtree: true }); bodyObserver.observe(document.body || document.documentElement, { childList: true, subtree: true }); } catch (e) { // ignore } // 首次尝试 tryOnce(); }, findStyleContaining(text) { const styles = document.querySelectorAll('style'); return Array.from(styles).find(style => style.textContent.includes(text) ); }, base64ToUint8Array(base64) { const data = window.atob(base64); return new Uint8Array([...data].map(char => char.charCodeAt(0))); }, createCharMap(font, table) { const charMap = {}; for (let i = 19968; i < 40870; i++) { const glyph = Typr.U.codeToGlyph(font, i); if (!glyph) continue; const path = Typr.U.glyphToPath(font, glyph); const pathHash = md5(JSON.stringify(path)).slice(24); if (table[pathHash]) { charMap[String.fromCharCode(i)] = String.fromCharCode(table[pathHash]); } } return charMap; }, replaceEncryptedText(charMap) { const elements = document.querySelectorAll('.font-cxsecret'); elements.forEach(element => { let html = element.innerHTML; Object.entries(charMap).forEach(([encrypted, decrypted]) => { html = html.replace(new RegExp(encrypted, 'g'), decrypted); }); element.innerHTML = html; element.classList.remove('font-cxsecret'); }); } }, // 粘贴 (强制启用粘贴功能) pasteEnabler: { init() { this.removeGlobalRestrictions(); this.injectGlobalStyles(); this.enableExistingElements(); this.startMutationObserver(); this.monitorUEditor(); }, removeGlobalRestrictions() { ['paste', 'contextmenu', 'selectstart', 'dragstart', 'copy', 'cut', 'keydown'] .forEach(event => document[`on${event}`] = null); this.removeElementRestrictions(document.body); }, injectGlobalStyles() { const style = document.createElement('style'); style.textContent = ` * { -webkit-user-select: text !important; -moz-user-select: text !important; -ms-user-select: text !important; user-select: text !important; } input, textarea, [contenteditable] { user-select: text !important; pointer-events: auto !important; } `; document.head.appendChild(style); }, removeElementRestrictions(element) { if (!element) return; // 移除事件监听器和属性 ['paste', 'contextmenu', 'keydown', 'selectstart', 'dragstart', 'copy', 'cut'] .forEach(event => { element[`on${event}`] = null; element.removeAttribute(`on${event}`); }); element.removeAttribute('readonly'); element.removeAttribute('disabled'); // 恢复样式 Object.assign(element.style, { userSelect: 'text', webkitUserSelect: 'text', mozUserSelect: 'text', msUserSelect: 'text', pointerEvents: 'auto' }); }, enableExistingElements() { document.querySelectorAll('input, textarea, [contenteditable]') .forEach(el => this.removeElementRestrictions(el)); }, startMutationObserver() { new MutationObserver(mutations => { mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { if (node.nodeType !== 1) return; if (node.matches?.('input, textarea, [contenteditable]')) { this.removeElementRestrictions(node); } if (node.tagName === 'IFRAME') { setTimeout(() => { try { node.contentDocument?.querySelectorAll('input, textarea, [contenteditable]') .forEach(el => this.removeElementRestrictions(el)); } catch (e) { // 跨域iframe,静默处理 } }, 500); } node.querySelectorAll?.('input, textarea, [contenteditable]') .forEach(el => this.removeElementRestrictions(el)); }); }); }).observe(document.body, { childList: true, subtree: true }); }, handlePaste(e) { e.preventDefault(); e.stopPropagation(); const doc = e.target.ownerDocument || document; const clipboardData = e.clipboardData || window.clipboardData; // 处理图片粘贴 const imageItem = Array.from(clipboardData?.items || []) .find(item => item.type?.startsWith('image/')); if (imageItem) { const imageFile = imageItem.getAsFile(); const reader = new FileReader(); reader.onload = () => this.insertImage(doc, reader.result); reader.readAsDataURL(imageFile); return; } // 处理文本粘贴 const text = clipboardData?.getData?.('text/plain') || window.clipboardData?.getData?.('Text') || ''; if (text) { this.insertText(doc, text); } }, insertImage(doc, dataUrl) { const img = doc.createElement('img'); img.src = dataUrl; img.alt = '粘贴图片'; const selection = doc.getSelection(); if (selection?.rangeCount > 0) { const range = selection.getRangeAt(0); range.deleteContents(); range.insertNode(img); range.setStartAfter(img); selection.removeAllRanges(); selection.addRange(range); } else { doc.body?.appendChild(img); } }, insertText(doc, text) { if (doc.execCommand?.('insertText', false, text)) return true; const selection = doc.getSelection(); if (selection?.rangeCount > 0) { const range = selection.getRangeAt(0); range.deleteContents(); const textNode = doc.createTextNode(text); range.insertNode(textNode); range.setStartAfter(textNode); selection.removeAllRanges(); selection.addRange(range); return true; } doc.body?.appendChild(doc.createTextNode(text)); return false; }, monitorUEditor() { let attempts = 0; const maxAttempts = 20; const checkUEditor = () => { attempts++; // 处理主窗口UEditor实例 if (typeof UE !== 'undefined' && UE.instants) { Object.values(UE.instants).forEach(instance => { this.processUEditorInstance(instance); }); } // 处理iframe中的UEditor实例 document.querySelectorAll('iframe').forEach(iframe => { try { const iframeWindow = iframe.contentWindow; if (iframeWindow?.UE?.instants) { Object.values(iframeWindow.UE.instants).forEach(instance => { this.processUEditorInstance(instance); }); } } catch (error) { // 跨域iframe,静默处理 } }); if (attempts < maxAttempts) setTimeout(checkUEditor, 500); }; checkUEditor(); }, processUEditorInstance(instance) { if (!instance || typeof instance !== 'object') return; // 启用编辑器 if (instance.options) { instance.options.pasteplain = true; instance.options.readonly = false; instance.options.disabled = false; } if (instance.setEnabled && instance.body) { instance.setEnabled(true); } // 处理编辑器主体 if (instance.body && !instance.body.__cxPasteEnabled) { this.removeElementRestrictions(instance.body); instance.body.addEventListener('paste', (e) => this.handlePaste(e), true); instance.body.__cxPasteEnabled = true; } // 处理iframe文档 const iframeDoc = instance.iframe?.contentDocument; if (iframeDoc && !iframeDoc.__cxPasteEnabled) { this.removeElementRestrictions(iframeDoc.body); iframeDoc.addEventListener('paste', (e) => this.handlePaste(e), true); iframeDoc.__cxPasteEnabled = true; } } }, // 一键复制所有题目 copyAllQuestion: { MESSAGE: { REQUEST: 'CX_GET_TITLES', RESPONSE: 'CX_TITLES_RESPONSE' }, init() { // 跨页面消息监听(子 iframe 也会加载本脚本) this.setupMessageHandler(); // 顶层页面插入复制按钮 this.insertCopyButton(); // 注入模态框 this.injectModal(); }, // 注入模态框的HTML和CSS injectModal() { const modalHtml = ` `; document.body.insertAdjacentHTML('beforeend', modalHtml); // 获取模态框元素 const modalOverlay = document.getElementById('cx-copy-modal-overlay'); const textarea = document.getElementById('cx-copy-modal-textarea'); const cancelButton = document.getElementById('cx-copy-modal-cancel'); const copyButton = document.getElementById('cx-copy-modal-copy'); // 取消按钮事件 cancelButton.addEventListener('click', () => { modalOverlay.style.display = 'none'; }); // 复制按钮事件 copyButton.addEventListener('click', async () => { try { await this.copyToClipboard(textarea.value); this.toast('已复制题目内容'); modalOverlay.style.display = 'none'; } catch (e) { this.toast('复制失败,请重试'); } }); // 点击遮罩层关闭模态框 modalOverlay.addEventListener('click', (e) => { if (e.target === modalOverlay) { modalOverlay.style.display = 'none'; } }); }, // 显示模态框并填充内容 showModal(content) { const modalOverlay = document.getElementById('cx-copy-modal-overlay'); const textarea = document.getElementById('cx-copy-modal-textarea'); if (modalOverlay && textarea) { textarea.value = content; modalOverlay.style.display = 'flex'; } }, setupMessageHandler() { window.addEventListener('message', (event) => { try { const data = event.data || {}; if (data?.type !== this.MESSAGE.REQUEST || !data.id) return; // 只响应超星域名消息 const host = new URL(event.origin).hostname || ''; if (!/chaoxing\.com$/.test(host)) return; const titles = this.extractTitlesFromDocument(document); // 兼容旧协议 const questions = this.extractQuestionsFromDocument(document); const header = this.findHeaderTitle(document) || document.title || ''; event.source?.postMessage({ type: this.MESSAGE.RESPONSE, id: data.id, titles, // 兼容旧协议 questions, header }, event.origin); } catch (err) { // 忽略跨域或解析错误 } }, false); }, insertCopyButton() { const h2s = Array.from(document.querySelectorAll('h2')); const anchor = h2s.find(h => h.textContent?.trim()?.includes('章节详情')); if (!anchor) return; if (document.getElementById('__cx_copy_all_btn')) return; const btn = document.createElement('button'); btn.id = '__cx_copy_all_btn'; btn.textContent = '点击复制所有题目'; btn.setAttribute('type', 'button'); btn.style.cssText = [ 'position: sticky', 'top: 8px', 'margin: 8px 0', 'padding: 6px 12px', 'background: #1677ff', 'color: #fff', 'border: none', 'border-radius: 4px', 'font-size: 14px', 'cursor: pointer', 'z-index: 9999' ].join(';'); btn.addEventListener('click', async () => { try { const content = await this.collectAllTitles(); this.showModal(content); // 显示模态框而不是直接复制 } catch (e) { this.toast('收集题目失败,请重试'); } }); anchor.insertAdjacentElement('afterend', btn); }, // 汇总顶层及所有 iframe 的题目 async collectAllTitles() { const sections = await this.collectTitlesFromDocument(document); const lines = []; sections.forEach(({ header, questions }) => { if (questions?.length) { if (header) { lines.push(`# ${header}`); lines.push(''); } questions.forEach((q, idx) => { const titleLine = `${idx + 1}. ${q.title}`; lines.push(titleLine); if (Array.isArray(q.options) && q.options.length) { q.options.forEach(opt => lines.push(opt)); } lines.push(''); }); } }); return lines.join('\n'); }, // 递归采集指定文档内所有题目(含嵌套 iframe) async collectTitlesFromDocument(doc) { const sections = []; const header = this.findHeaderTitle(doc) || doc.title || ''; const questions = this.extractQuestionsFromDocument(doc); if (questions.length) sections.push({ header, questions }); const frames = Array.from(doc.querySelectorAll('iframe')); for (const frame of frames) { try { const fdoc = frame.contentDocument; if (fdoc) { const childSections = await this.collectTitlesFromDocument(fdoc); sections.push(...childSections); } else { const res = await this.collectFromFrame(frame); if (res?.questions?.length) sections.push(res); } } catch (_) { const res = await this.collectFromFrame(frame); if (res?.questions?.length) sections.push(res); } } return sections; }, collectFromFrame(frame) { // 优先同源直接采集 try { const doc = frame.contentDocument; if (doc) { const questions = this.extractQuestionsFromDocument(doc); const header = this.findHeaderTitle(doc) || doc.title || ''; return Promise.resolve({ header, questions }); } } catch (_) { // frame } // 跨域使用 postMessage 请求 const id = Math.random().toString(36).slice(2); return new Promise((resolve) => { const onMessage = (event) => { const data = event.data || {}; if (data?.type === this.MESSAGE.RESPONSE && data.id === id) { window.removeEventListener('message', onMessage); const questions = Array.isArray(data.questions) ? data.questions : (data.titles || []).map(t => ({ title: t, options: [], type: 'other' })); resolve({ header: data.header || '', questions }); } }; window.addEventListener('message', onMessage); // 超时保护 const timer = setTimeout(() => { window.removeEventListener('message', onMessage); resolve({ header: '', titles: [] }); }, 3000); try { frame.contentWindow?.postMessage({ type: this.MESSAGE.REQUEST, id }, '*'); } catch (e) { clearTimeout(timer); window.removeEventListener('message', onMessage); resolve({ header: '', titles: [] }); } }); }, extractTitlesFromDocument(doc) { // 覆盖更多页面结构(老版/新版、题目容器、TiMu 包裹等) const primarySelectors = [ '.Zy_TItle .fontLabel', '.Zy_Title .fontLabel', '.newZy_TItle .fontLabel', '.newZy_Title .fontLabel', '.TiMu .Zy_TItle .fontLabel', '.TiMu .Zy_Title .fontLabel', '[class*="Zy_"][class*="TItle"] .fontLabel', '[class*="Zy_"][class*="Title"] .fontLabel' ]; const fallbackSelectors = [ '.Zy_TItle', '.Zy_Title', '.newZy_TItle', '.newZy_Title', '.TiMu .Zy_TItle', '.TiMu .Zy_Title' ]; const set = new Set(); // 先从 fontLabel(解密字体)中提取 primarySelectors.forEach(sel => { doc.querySelectorAll(sel).forEach(n => { const text = (n.textContent || '').replace(/\s+/g, ' ').trim(); if (text) set.add(text); }); }); // 若未命中,则退回到题目容器本身文本 if (set.size === 0) { fallbackSelectors.forEach(sel => { doc.querySelectorAll(sel).forEach(n => { const text = (n.textContent || '').replace(/\s+/g, ' ').trim(); if (text) set.add(text); }); }); } return Array.from(set); }, // 提取选择题与简答题为结构化数据 extractQuestionsFromDocument(doc) { const questions = []; const containers = Array.from(doc.querySelectorAll('.TiMu')); const getTitleFrom = (root) => { const titleNode = root.querySelector('.Zy_TItle .fontLabel, .Zy_Title .fontLabel, .newZy_TItle .fontLabel, .newZy_Title .fontLabel') || root.querySelector('.Zy_TItle, .Zy_Title, .newZy_TItle, .newZy_Title'); return (titleNode?.textContent || '').replace(/\s+/g, ' ').trim(); }; if (containers.length > 0) { containers.forEach(box => { const title = getTitleFrom(box); if (!title) return; const ul = box.querySelector('ul.Zy_ulTop.w-top.fl'); const lis = ul ? Array.from(ul.querySelectorAll(':scope > li')) : []; const options = lis.map((li, idx) => { const p = li.querySelector('p') || li.querySelector('a') || li; const text = (p?.textContent || '').replace(/\s+/g, ' ').trim(); const letter = String.fromCharCode(65 + idx); // A,B,C... return `${letter}. ${text}`; }).filter(Boolean); let type = 'other'; if (options.length === 4) type = 'single'; else if (options.length >= 5) type = 'multi'; questions.push({ title, options, type }); }); } // 若无 TiMu 容器,退回到标题列表作为简答题/其他题 if (questions.length === 0) { const titles = this.extractTitlesFromDocument(doc); titles.forEach(t => questions.push({ title: t, options: [], type: 'other' })); } return questions; }, findHeaderTitle(doc) { // 子页面:作业标题常在 .ceyan_name h3 或 .newTestTitle const h3 = doc.querySelector('.ceyan_name h3'); if (h3?.textContent) return h3.textContent.trim(); const testName = doc.querySelector('.newTestTitle .TestTitle_name'); if (testName?.textContent) return testName.textContent.trim(); // 顶层列表页面:寻找"章节详情" const h2 = Array.from(doc.querySelectorAll('h2')).find(h => h.textContent?.includes('章节详情')); if (h2?.textContent) return h2.textContent.trim(); return ''; }, async copyToClipboard(text) { try { if (navigator.clipboard?.writeText) { await navigator.clipboard.writeText(text); return true; } } catch (_) { //copy not success } const ta = document.createElement('textarea'); ta.value = text; ta.style.position = 'fixed'; ta.style.top = '-9999px'; document.body.appendChild(ta); ta.select(); try { document.execCommand('copy'); } catch (_) { // copy } document.body.removeChild(ta); return true; }, toast(msg) { try { const el = document.createElement('div'); el.textContent = msg; el.style.cssText = [ 'position: fixed', 'left: 50%','top: 24px','transform: translateX(-50%)', 'background: rgba(0,0,0,0.75)', 'color: #fff','padding: 8px 12px','border-radius: 4px', 'font-size: 13px','z-index: 10000' ].join(';'); document.body.appendChild(el); setTimeout(() => el.remove(), 1500); } catch (_) { // msg } } }, // 主初始化 init() { try { this.pasteEnabler.init(); } catch (e) { console.error('[Chaoxing Helper] pasteEnabler 初始化失败:', e); } try { this.copyEnabler.watch(); } catch (e) { console.error('[Chaoxing Helper] copyEnabler 初始化失败:', e); } try { this.copyAllQuestion.init(); } catch (e) { console.error('[Chaoxing Helper] copyAllQuestion 初始化失败:', e); } console.log('[Chaoxing Helper] Done'); } }; // 启动 - 确保在 DOM 加载后执行 const start = () => { if (!document || document.__cxHelperStarted) return; document.__cxHelperStarted = true; ChaoxingHelper.init(); }; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', start); } else { // 如果 DOM 已经加载完成,延迟一点执行以确保所有资源都已加载 setTimeout(start, 100); } })(); // ==================== 由衷感谢iPycc ====================