// ==UserScript== // @name 超星学习通未答题任务标记 // @version 1.0 // @match https://mooc2-ans.chaoxing.com/mooc2-ans/mycourse/* // @grant GM_xmlhttpRequest // @connect mobilelearn.chaoxing. // @license MIT // @description 自动标记超星学习通中未答题任务标记 // @author Kaesinol // ==/UserScript== (() => { const TTL = 7 * 24 * 60 * 60 * 1000; const log = (...args) => console.log('[CX Answer]', ...args); const queue = []; const pending = new Map(); const seen = new WeakSet(); const observedDocs = new WeakSet(); const observedFrames = new WeakSet(); let running = false; let stopped = false; const markRed = li => li.style.outline = '2px solid red'; function cached(id) { const key = `cx-answer:${id}`; try { const value = JSON.parse(localStorage.getItem(key)); if (value?.answered === true && Date.now() - value.time >= 0 && Date.now() - value.time < TTL) return true; if (value !== null) localStorage.removeItem(key); } catch { localStorage.removeItem(key); } return false; } function enqueue(li) { const id = li.getAttribute('activeid'); if (!id || seen.has(li)) return; seen.add(li); if (cached(id)) { log('缓存命中:', id); return; } if (stopped) { markRed(li); return; } if (pending.has(id)) { pending.get(id).lis.add(li); return; } const item = { id, lis: new Set([li]) }; pending.set(id, item); queue.push(item); pump(); } function pump() { if (running || stopped || !queue.length) return; const item = queue.shift(); const { id, lis } = item; running = true; log('请求:', id, '队列剩余:', queue.length); GM_xmlhttpRequest({ method: 'GET', url: `https://mobilelearn.chaoxing.com/v2/apis/studentQuestion/getAnswerResult?activeId=${id}`, withCredentials: true, onload(r) { let json; try { json = JSON.parse(r.responseText); } catch (e) { console.error('[CX Answer] JSON 解析失败:', id, e, r.responseText); lis.forEach(markRed); finish(item); return; } console.log('[CX Answer] JSON:', id, json); if (json?.result === 0 || json?.result === '0') { stopped = true; log('result=0,停止所有后续请求:', json.errorMsg); lis.forEach(markRed); queue.forEach(item => item.lis.forEach(markRed)); queue.length = 0; pending.clear(); running = false; return; } if (json?.data?.isAnswered === true) { localStorage.setItem(`cx-answer:${id}`, JSON.stringify({ answered: true, time: Date.now() })); log('已作答,缓存 7 天:', id); } else { log('未作答或无法获取状态,标红:', id); lis.forEach(markRed); } finish(item); }, onerror(e) { console.error('[CX Answer] 请求失败:', id, e); lis.forEach(markRed); finish(item); } }); } function finish(item) { pending.delete(item.id); running = false; pump(); } function scan(doc) { const list = doc.querySelectorAll('li[activeid][activestatus="1"]:not(:has(div[aria-label="签到"]))'); list.forEach(enqueue); doc.querySelectorAll('iframe').forEach(frame => { if (!observedFrames.has(frame)) { observedFrames.add(frame); frame.addEventListener('load', () => { try { observe(frame.contentDocument); } catch {} }); } try { if (frame.contentDocument) observe(frame.contentDocument); } catch {} }); } function observe(doc) { if (!doc || observedDocs.has(doc)) return; if (!doc.documentElement) { doc.addEventListener('DOMContentLoaded', () => observe(doc), { once: true }); return; } observedDocs.add(doc); scan(doc); new MutationObserver(() => scan(doc)).observe(doc.documentElement, { childList: true, subtree: true }); } log('脚本启动:', location.href); observe(document); })();