// ==UserScript==
// @name 新国开/国开/国家开放大学自动刷课+次数和时长(试用卡版)
// @namespace https://scriptcat.org/
// @version 5.5.1
// @description 新国开|国开|国家开放大学|浅色/深色主题|点击绿框保持|授权后全自动展开→选择→点击|暂停继续|2/4/6/8/10倍速|10分钟保活|门户页操作指引
// @author You
// @match *://lms.ouchn.cn/*
// @match *://menhu.pt.ouchn.cn/*
// @noframes
// @icon https://raw.githubusercontent.com/yrtyrtyrtygfr/cjtfky/main/gd1.png
// @grant GM_xmlhttpRequest
// @connect aitwo.icu
// @run-at document-start
// @license MPL-2.0
// ==/UserScript==
(function () {
'use strict';
if (window.__QCC_INSTALLED__) return;
window.__QCC_INSTALLED__ = true;
/* ===== 常量 ===== */
const SERVER = 'https://aitwo.icu';
const HB_MS = 50000, HB_FAIL = 3;
const RAND_MIN = 2, RAND_MAX = 12;
// ★ 修改1:把等待视频的超时时间从 20 秒缩短到 8 秒,防止空等太久
const V_WAIT = 3000, V_POLL = 800;
const EXPAND_LIMIT = 5800, EXPAND_LOOPS = 50, EXPAND_GAP = 25, EXPAND_LOOP_GAP = 100, EXPAND_ROUND_GAP = 2000;
const LOG_MAX = 80;
const SPEEDS = [2, 4, 6, 8, 10];
const V_GRACE = 15000, V_STUCK = 30;
const TRIAL = 'CS-2VJY5A5T7Q';
const UI_SCALE = 1.00;
const K = { AUTH: 'qcc_auth_code_v3', EXP: 'qcc_auth_expire_v3', DEV: 'qcc_device_id', CFG: 'qcc_cfg_v2', THEME: 'qcc_theme_v1' };
/* ===== 状态 ===== */
let authCode = '', authorized = false;
let heartbeatTimer = null, hbFail = 0;
let syncAuthUI = null;
let isDarkTheme = false;
let isRunning = false;
const fakeFocus = () => {
if (!isRunning) return;
try {
Object.defineProperty(document, 'hidden', { configurable: true, get: () => false });
Object.defineProperty(document, 'visibilityState', { configurable: true, get: () => 'visible' });
Object.defineProperty(document, 'hasFocus', { configurable: true, get: () => true });
} catch (e) {}
};
window.addEventListener('blur', e => {
if (isRunning) { e.stopImmediatePropagation(); e.preventDefault(); fakeFocus(); }
}, true);
document.addEventListener('visibilitychange', e => {
if (isRunning) { e.stopImmediatePropagation(); fakeFocus(); }
}, true);
const DEVICE_ID = (() => {
try {
let id = localStorage.getItem(K.DEV);
if (id) return id;
try {
const m = document.cookie.match(new RegExp('(?:^|;\\s*)' + K.DEV + '=([^;]+)'));
if (m && m[1]) return m[1];
} catch (e) {}
const parts = [navigator.hardwareConcurrency || 0, screen.width + 'x' + screen.height, screen.colorDepth, new Date().getTimezoneOffset(), navigator.platform || '', navigator.language || ''].join('|');
let h = 0;
for (let i = 0; i < parts.length; i++) h = ((h << 5) - h + parts.charCodeAt(i)) | 0;
const fp = (h >>> 0).toString(16).toUpperCase().padStart(8, '0');
const rand = Math.random().toString(16).slice(2, 8).toUpperCase();
id = fp + '-' + rand + 'FP';
try { localStorage.setItem(K.DEV, id); } catch (e) {}
try { document.cookie = `${K.DEV}=${id}; max-age=31536000; path=/`; } catch (e) {}
return id;
} catch (e) { return 'FB' + Date.now().toString(16).slice(-6).toUpperCase() + 'FP'; }
})();
/* ===== 跳转续跑 ===== */
const K_RESUME = 'qcc_resume_v1';
function setResume() { try { sessionStorage.setItem(K_RESUME, '1'); } catch (e) {} }
function getResume() { try { return sessionStorage.getItem(K_RESUME) === '1'; } catch (e) { return false; } }
function clearResume() { try { sessionStorage.removeItem(K_RESUME); } catch (e) {} }
/* ===== 页面识别 ===== */
function findExpandAllButton() {
let all; try { all = document.querySelectorAll('button, a'); } catch (e) { return null; }
let best = null, bestLen = Infinity;
for (const el of all) {
if (panel && panel.contains(el)) continue;
const t = (el.textContent || '').trim().replace(/\s+/g, '');
if (!t.includes('全部展开')) continue;
if (t.length > 20) continue;
if (t.length < bestLen) { bestLen = t.length; best = el; }
}
return best;
}
function findCollapseAllButton() {
const all = document.querySelectorAll('button, a, span, div');
let best = null, bestLen = Infinity;
for (const el of all) {
if (panel && panel.contains(el)) continue;
const t = (el.textContent || '').trim().replace(/\s+/g, '');
if (!t.includes('全部收起')) continue;
if (t.length > 20) continue;
if (t.length < bestLen) { bestLen = t.length; best = el; }
}
return best;
}
function findReturnCourseButton() {
const all = document.querySelectorAll('button, a, span, div');
let best = null, bestLen = Infinity;
for (const el of all) {
if (panel && panel.contains(el)) continue;
const t = (el.textContent || '').trim().replace(/\s+/g, '');
if (!t.includes('返回课程')) continue;
if (t.length > 20) continue;
if (t.length < bestLen) { bestLen = t.length; best = el; }
}
return best;
}
function isLearningActivityPage() {
return /\/learning-activity\//.test(location.href) || !!findReturnCourseButton();
}
function isChapterListPage() {
return !!findExpandAllButton() || !!findCollapseAllButton();
}
function clickExpandAll() {
const btn = findExpandAllButton();
if (!btn) return false;
try { realClick(btn); } catch (e) { try { btn.click(); } catch (e2) {} }
return true;
}
async function waitForCollapseAll(maxMs = 5000) {
const st = Date.now();
while (Date.now() - st < maxMs) {
if (findCollapseAllButton()) return true;
await sleep(200);
}
return false;
}
function findFirstEntry() {
const cands = document.querySelectorAll('a.title, .activity-title a, .activity-header a');
const list = [];
for (const a of cands) {
if (panel && panel.contains(a)) continue;
let r;
try { r = a.getBoundingClientRect(); } catch (e) { continue; }
if (r.width < 30 || r.height < 12) continue;
if (r.left < 180) continue;
if (r.top < 100) continue;
const t = (a.textContent || '').trim().replace(/\s+/g, ' ');
if (!t || t.length > 100) continue;
if (/全部展开|全部收起|形考任务|终考任务|讨论/.test(t)) continue;
list.push({ el: a, top: r.top, left: r.left });
}
list.sort((a, b) => a.top - b.top || a.left - b.left);
return list.length ? list[0].el : null;
}
/* ===== 主题 ===== */
function loadTheme() {
try {
const t = localStorage.getItem(K.THEME);
if (t === 'dark') isDarkTheme = true;
else if (t === 'light') isDarkTheme = false;
else isDarkTheme = !!(window.matchMedia && window.matchMedia('(prefers-color-scheme:dark)').matches);
} catch (e) { isDarkTheme = false; }
}
function applyTheme() {
if (!panel) return;
panel.classList.toggle('qcc-dark', isDarkTheme);
const toggle = document.getElementById('qcc-theme-toggle');
if (toggle) toggle.checked = isDarkTheme;
try {
panel.querySelectorAll('.qcc-theme-label').forEach(l => {
const isDarkLabel = l.dataset.theme === 'dark';
l.classList.toggle('active', isDarkTheme === isDarkLabel);
});
} catch (e) {}
try { localStorage.setItem(K.THEME, isDarkTheme ? 'dark' : 'light'); } catch (e) {}
}
/* ===== 授权 ===== */
const api = (path, data) => new Promise((res, rej) => {
GM_xmlhttpRequest({
method: 'POST', url: SERVER + path, headers: { 'Content-Type': 'application/json' },
data: JSON.stringify(data || {}), timeout: 10000,
onload: r => { try { res(JSON.parse(r.responseText || '{}')); } catch (e) { rej(); } },
onerror: rej, ontimeout: rej
});
});
const verify = async code => { try { const r = await api('/api/verify', { card: code, mac: DEVICE_ID }); return r && typeof r === 'object' ? r : null; } catch (e) { return null; } };
async function doVerify(raw) {
const code = (raw || '').trim();
if (!code) return { ok: false, msg: '请输入卡密' };
const res = await verify(code);
if (!res) return { ok: false, msg: '❌ 网络请求失败' };
if (res.ok) {
authorized = true; authCode = code;
localStorage.setItem(K.AUTH, code);
let days = NaN, lifetime = false;
if (typeof res.days === 'number') days = res.days;
else if (typeof res.days === 'string' && res.days !== 'lifetime') days = parseInt(res.days, 10);
else if (typeof res.remaining_days === 'number') days = res.remaining_days;
else if (typeof res.remaining === 'number') days = res.remaining;
else if (typeof res.duration === 'number') days = res.duration;
if (res.lifetime === true || res.days === -1 || res.days === 'lifetime') lifetime = true;
if (typeof res.expire === 'number' && res.expire > 1e12) localStorage.setItem(K.EXP, String(res.expire));
else if (typeof res.expire === 'string' && !isNaN(Date.parse(res.expire))) localStorage.setItem(K.EXP, String(Date.parse(res.expire)));
else if (lifetime) localStorage.setItem(K.EXP, 'lifetime');
else if (!isNaN(days) && days > 0) localStorage.setItem(K.EXP, String(Date.now() + days * 864e5));
else localStorage.removeItem(K.EXP);
startHB();
if (syncAuthUI) try { syncAuthUI(); } catch (e) {}
const tail = lifetime ? '(终身有效)' : (!isNaN(days) && days > 0 ? `(剩余 ${days} 天)` : '(已授权)');
return { ok: true, msg: '✅ ' + (res.msg || '验证通过') + tail };
}
return { ok: false, msg: '❌ ' + (res.msg || '验证失败') };
}
function startHB() {
stopHB(); hbFail = 0;
heartbeatTimer = setInterval(async () => {
if (!authorized || !authCode) return;
const r = await verify(authCode);
if (r && r.ok) { hbFail = 0; return; }
if (++hbFail >= HB_FAIL) {
authorized = false; stopHB();
if (typeof stopClicking === 'function') stopClicking();
if (syncAuthUI) try { syncAuthUI(); } catch (e) {}
setStatus('⚠️ 网络异常,卡密已保留', '');
addLog('⚠️ 心跳失败(已保留卡密)', 'err');
}
}, HB_MS);
}
function stopHB() { if (heartbeatTimer) { clearInterval(heartbeatTimer); heartbeatTimer = null; } hbFail = 0; }
async function loadAuth() {
let saved = '';
try { saved = (localStorage.getItem(K.AUTH) || '').trim(); } catch (e) { return; }
if (!saved) return;
const ex = parseInt(localStorage.getItem(K.EXP) || '0', 10);
if (ex && ex < Date.now()) { try { localStorage.removeItem(K.AUTH); localStorage.removeItem(K.EXP); } catch (e) {} return; }
const r = await verify(saved);
if (!r || !r.ok) { authCode = saved; return; }
authCode = saved; authorized = true; startHB();
}
/* ===== 配置 ===== */
function loadCfg() {
try {
const c = JSON.parse(localStorage.getItem(K.CFG) || '{}');
if (typeof c.autoPlayVideo === 'boolean') autoPlayVideo = c.autoPlayVideo;
if (SPEEDS.includes(c.videoSpeed)) videoSpeed = c.videoSpeed;
if (typeof c.maxClicks === 'number' && c.maxClicks >= 0) maxClicks = c.maxClicks;
} catch (e) {}
}
const saveCfg = () => { try { localStorage.setItem(K.CFG, JSON.stringify({ autoPlayVideo, videoSpeed, maxClicks })); } catch (e) {} };
function getAllRoots() { return [document]; }
const reportErr = (tag, err, log) => { console.warn(`[QCC] ${tag}:`, err); if (log) addLog(`⚠️ ${tag}:${(err && err.message) || err}`, 'err'); };
function realClick(el) {
if (!el) return false;
try {
const r0 = el.getBoundingClientRect();
if (r0.top < 0 || r0.bottom > innerHeight || r0.left < 0 || r0.right > innerWidth) {
try { el.scrollIntoView({ block: 'center', inline: 'center', behavior: 'instant' }); } catch (e) { try { el.scrollIntoView(); } catch (e2) {} }
}
} catch (e) {}
let r;
try { r = el.getBoundingClientRect(); } catch (e) { r = { left: 0, top: 0, width: 1, height: 1 }; }
const x = r.left + r.width / 2, y = r.top + r.height / 2;
const base = { bubbles: true, cancelable: true, composed: true, clientX: x, clientY: y, screenX: x, screenY: y, button: 0, detail: 1 };
try {
if (window.PointerEvent) { try { el.dispatchEvent(new PointerEvent('pointerdown', Object.assign({}, base, { buttons: 1, pointerId: 1, pointerType: 'mouse', isPrimary: true }))); } catch (e) {} }
el.dispatchEvent(new MouseEvent('mousedown', Object.assign({}, base, { buttons: 1 })));
el.dispatchEvent(new MouseEvent('mouseup', Object.assign({}, base, { buttons: 0 })));
el.dispatchEvent(new MouseEvent('click', Object.assign({}, base, { buttons: 0 })));
} catch (e) { return false; }
return true;
}
const pickClick = el => { let c = el; for (let i = 0; i < 4 && c; i++) { try { if (getComputedStyle(c).cursor === 'pointer') return c; } catch (e) {} c = c.parentElement; } return el; };
const isBold = el => {
if (!el || el.nodeType !== 1) return false;
const t = (el.tagName || '').toUpperCase();
if (t === 'STRONG' || t === 'B' || /^H[1-6]$/.test(t)) return true;
try { const n = parseInt(getComputedStyle(el).fontWeight, 10) || 0; return n >= 600; } catch (e) { return false; }
};
const randDelay = () => Math.round((Math.random() * (RAND_MAX - RAND_MIN) + RAND_MIN) * 1000);
const workerSrc = `let t=null;self.onmessage=e=>{if(e.data.cmd==='start'){if(t)clearInterval(t);t=setInterval(()=>self.postMessage({tick:1}),e.data.ms||1000);}else if(e.data.cmd==='stop'){if(t){clearInterval(t);t=null;}}};`;
let worker = null;
const getWorker = () => {
if (worker) return worker;
try { worker = new Worker(URL.createObjectURL(new Blob([workerSrc], { type: 'application/javascript' }))); } catch (e) { worker = null; }
return worker;
};
let delayResolve = null, delayTimer = null;
function interruptDelay() { if (delayTimer) { clearTimeout(delayTimer); delayTimer = null; } if (delayResolve) { const r = delayResolve; delayResolve = null; r(); } }
function waitDelay(ms) {
return new Promise(res => {
delayResolve = () => { delayResolve = null; res(); };
const w = getWorker();
if (w) {
const h = e => {
if (e.data && e.data.tick) {
w.removeEventListener('message', h);
try { w.postMessage({ cmd: 'stop' }); } catch (_) {}
if (delayResolve) { const r = delayResolve; delayResolve = null; r(); }
}
};
w.addEventListener('message', h, { once: true });
try { w.postMessage({ cmd: 'start', ms }); } catch (e) {
try { w.removeEventListener('message', h); } catch (e2) {}
delayTimer = setTimeout(() => { if (delayResolve) { const r = delayResolve; delayResolve = null; r(); } }, ms);
}
} else delayTimer = setTimeout(() => { if (delayResolve) { const r = delayResolve; delayResolve = null; r(); } }, ms);
});
}
/* ===== 保活 ===== */
let keepAudio = null, wakeLock = null, visPatched = false, pauseHardPatched = false;
let superTimer = null, markerTimer = null, recoverCount = 0;
const vMeta = new WeakMap();
function patchVisibility() { if (visPatched) return; visPatched = true; fakeFocus(); }
function patchPause() {
if (window.__QCC_PAUSE__) return;
window.__QCC_PAUSE__ = true;
try {
const orig = HTMLMediaElement.prototype.pause;
HTMLMediaElement.prototype.pause = function () {
if (!isRunning || this.ended) return orig.call(this);
if (document.hidden) return;
return orig.call(this);
};
} catch (e) {}
}
function patchPauseHard() {
if (pauseHardPatched) return; pauseHardPatched = true;
try {
const cur = HTMLMediaElement.prototype.pause;
HTMLMediaElement.prototype.pause = function () {
const m = vMeta.get(this);
if (m && !m.giveUp && !this.ended) return;
return cur.call(this);
};
Object.defineProperty(HTMLMediaElement.prototype, 'paused', {
get: function() {
if (isRunning && this.tagName === 'VIDEO') return false;
return Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'paused').get.call(this);
},
configurable: true
});
} catch (e) {}
}
function markVideos() { try { document.querySelectorAll('video').forEach(v => { if (!vMeta.has(v)) vMeta.set(v, { last: 0, stuck: 0, giveUp: false }); }); } catch (e) {} }
function startSuperKA() {
if (superTimer) return;
patchPauseHard(); markVideos(); recoverCount = 0;
superTimer = setInterval(() => {
if (!isRunning) return;
try {
document.querySelectorAll('video').forEach(v => {
let m = vMeta.get(v);
if (!m) { m = { last: 0, stuck: 0, giveUp: false }; vMeta.set(v, m); }
if (m.giveUp) return;
const ct = v.currentTime || 0;
m.stuck = (m.last === ct) ? m.stuck + 1 : 0;
m.last = ct;
if (v.paused && !v.ended && ct > 0) { recoverCount++; v.play().catch(() => {}); }
if (m.stuck >= 30 && !m.giveUp) { m.giveUp = true; addLog('⚠️ 视频卡顿', 'err'); }
});
} catch (e) {}
}, 1000);
markerTimer = setInterval(markVideos, 5000);
}
function stopSuperKA() { if (superTimer) { clearInterval(superTimer); superTimer = null; } if (markerTimer) { clearInterval(markerTimer); markerTimer = null; } recoverCount = 0; }
function startMediaSession() { try { if ('mediaSession' in navigator) { navigator.mediaSession.playbackState = 'playing'; try { navigator.mediaSession.metadata = new MediaMetadata({ title: '学习视频', artist: '国开' }); } catch (e) {} } } catch (e) {} }
async function reqWakeLock() { try { if ('wakeLock' in navigator && !wakeLock) { wakeLock = await navigator.wakeLock.request('screen'); wakeLock.addEventListener('release', () => { wakeLock = null; if (isRunning) setTimeout(reqWakeLock, 1000); }); } } catch (e) {} }
function startKA() { if (keepAudio) return; try { const ctx = new (window.AudioContext || window.webkitAudioContext)(); if (ctx.state === 'suspended') ctx.resume().catch(() => {}); const osc = ctx.createOscillator(), g = ctx.createGain(); osc.type = 'sine'; osc.frequency.value = 440; g.gain.value = 0.00005; osc.connect(g).connect(ctx.destination); osc.start(); const rt = setInterval(() => { try { if (ctx.state === 'suspended') ctx.resume().catch(() => {}); } catch (e) {} }, 30000); keepAudio = { ctx, osc, rt }; } catch (e) {} }
function stopKA() { try { if (keepAudio) { clearInterval(keepAudio.rt); keepAudio.osc.stop(); keepAudio.ctx.close(); } } catch (e) {} keepAudio = null; }
function enableKA() { patchVisibility(); patchPause(); startMediaSession(); startKA(); reqWakeLock(); startSuperKA(); }
function disableKA() { stopKA(); try { if (wakeLock) { wakeLock.release(); wakeLock = null; } } catch (e) {} stopSuperKA(); }
/* ===== 全局状态 ===== */
let panel, statusEl, statusTextEl, listCountEl;
let selectedEls = [], isPaused = false;
let clickTimer = null, clickIndex = 0, clickedTotal = 0, maxClicks = 0;
let expanding = false, picking = false, processingVideo = false, preparing = false;
let currentClickingEl = null, clickHighlightTimer = null;
const clickCountMap = new WeakMap();
const sleep = ms => new Promise(r => setTimeout(r, ms));
function calcScale() { const w = innerWidth, h = innerHeight; let s = w >= 3600 ? 1.40 : w >= 2800 ? 1.25 : w >= 2200 ? 1.10 : w >= 1920 ? 1.00 : w >= 1600 ? 0.92 : w >= 1400 ? 0.85 : w >= 1200 ? 0.78 : 0.72; if (h < 720 && s > 0.9) s = 0.9; if (h < 600 && s > 0.8) s = 0.8; return s * UI_SCALE; }
function applyScale() { if (!panel) return; const s = calcScale(); if ('zoom' in panel.style) panel.style.zoom = s; else { panel.style.transformOrigin = 'top right'; panel.style.transform = `scale(${s})`; } }
/* ===== 统计 ===== */
const runStats = { start: 0, last: 0, vCount: 0, vSec: 0 };
let logEntries = [], logListEl = null, statRT = null, statV = null, statVC = null, statC = null;
let logTimer = null;
const _vRec = new WeakSet();
const fmtHMS = s => { s = Math.max(0, Math.floor(s || 0)); return `${String(Math.floor(s / 3600)).padStart(2, '0')}:${String(Math.floor((s % 3600) / 60)).padStart(2, '0')}:${String(s % 60).padStart(2, '0')}`; };
const fmtMS = s => { s = Math.max(0, s || 0); if (s < 60) return `${s.toFixed(0)}秒`; const m = s / 60; return m < 60 ? `${m.toFixed(1)}分钟` : `${Math.floor(m / 60)}小时${(m % 60).toFixed(0)}分`; };
function addLog(text, type) {
const time = new Date().toLocaleTimeString('zh-CN', { hour12: false });
logEntries.push({ time, text, type: type || '' });
if (logEntries.length > LOG_MAX) { logEntries.shift(); renderLog(); return; }
if (!logListEl) return;
if (logEntries.length === 1) logListEl.innerHTML = '';
const row = document.createElement('div'); row.className = 'qcc-log-item' + (type ? ' ' + type : '');
const t = document.createElement('span'); t.className = 'qcc-log-time'; t.textContent = time;
const x = document.createElement('span'); x.className = 'qcc-log-text'; x.textContent = text;
row.append(t, x);
logListEl.insertBefore(row, logListEl.firstChild);
}
function renderLog() {
if (!logListEl) return;
if (!logEntries.length) { logListEl.innerHTML = '
暂无日志
'; return; }
logListEl.innerHTML = logEntries.slice().reverse().map(e => {
const cls = 'qcc-log-item' + (e.type ? ' ' + e.type : '');
return `${e.time}${e.text.replace(/
`;
}).join('');
}
function updateStats() {
if (!statRT) return;
const rt = isRunning ? (Date.now() - runStats.start) / 1000 : runStats.last;
statRT.textContent = fmtHMS(rt);
statV.textContent = fmtMS(runStats.vSec);
statVC.textContent = runStats.vCount + '个';
statC.textContent = clickedTotal + '次';
}
function resetStats() { logEntries = []; runStats.vCount = 0; runStats.vSec = 0; runStats.last = 0; if (isRunning) runStats.start = Date.now(); clickedTotal = 0; renderLog(); updateStats(); }
function recVideo(video, sec) {
if (!video || _vRec.has(video)) return;
_vRec.add(video);
let w = sec;
if (typeof w !== 'number' || !isFinite(w) || w < 1) { try { const ct = video.currentTime || 0, d = video.duration; w = (isFinite(d) && d > 0) ? Math.min(ct, d) : ct; } catch (e) { w = 0; } }
if (!isFinite(w) || w < 1) return;
runStats.vSec += w; runStats.vCount++;
addLog(`🎬 视频完成 +${fmtMS(w)}(累计 ${fmtMS(runStats.vSec)} / ${runStats.vCount}个)`, 'ok');
updateStats();
}
function updateAuthTime() {
const at = document.getElementById('qcc-auth-time');
if (!at) return;
let info;
if (!authorized) info = authCode ? { text: '待验证', cls: 'warn' } : { text: '未授权', cls: '' };
else {
const raw = localStorage.getItem(K.EXP) || '';
if (raw === 'lifetime') info = { text: 'VIP · 终身有效', cls: 'ok' };
else {
const ex = parseInt(raw, 10);
if (!ex || ex <= 0) info = { text: 'VIP · 已授权', cls: 'ok' };
else {
const left = ex - Date.now();
if (left <= 0) info = { text: 'VIP · 已过期', cls: 'err' };
else { const d = Math.ceil(left / 864e5); if (d > 30) { const dt = new Date(ex); info = { text: `VIP · 至 ${dt.getFullYear()}/${String(dt.getMonth() + 1).padStart(2, '0')}/${String(dt.getDate()).padStart(2, '0')}`, cls: 'ok' }; } else if (d > 3) info = { text: `VIP · 剩余 ${d} 天`, cls: 'ok' }; else info = { text: `VIP · 仅剩 ${d} 天`, cls: 'warn' }; }
}
}
}
at.textContent = info.text;
at.className = 'qcc-auth-time' + (info.cls ? ' ' + info.cls : '');
}
function setStatus(text, cls) { if (!statusTextEl) return; statusTextEl.textContent = text; statusEl.className = 'qcc-status' + (cls ? ' ' + cls : ''); }
function getElText(el) { if (!el) return '未知元素'; const t = (el.textContent || '').trim().replace(/\s+/g, ' '); return t ? (t.length > 30 ? t.slice(0, 30) + '…' : t) : `[${(el.tagName || 'el').toLowerCase()}]`; }
function renderList() { if (!listCountEl) return; listCountEl.textContent = selectedEls.length + '个'; }
function clearClickHighlight() { if (clickHighlightTimer) { clearInterval(clickHighlightTimer); clickHighlightTimer = null; } try { document.querySelectorAll('.qcc-clicking').forEach(x => x.classList.remove('qcc-clicking')); } catch (e) {} currentClickingEl = null; }
function clearSelected() { clearClickHighlight(); selectedEls.forEach(el => { try { el.classList.remove('qcc-pick-selected'); } catch (e) {} }); selectedEls = []; renderList(); }
function updateRunButtons() {
const b1 = document.getElementById('qcc-start'), b2 = document.getElementById('qcc-stop'), b3 = document.getElementById('qcc-pause');
if (b1) { b1.disabled = isRunning; b1.innerHTML = preparing ? '⏳ 准备中…' : '▶ 开始'; }
if (b2) b2.disabled = false;
if (b3) { b3.disabled = !isRunning || preparing; b3.innerHTML = isPaused ? '▶ 继续' : '⏸ 暂停'; }
}
let manualPicking = false, hoverEl = null, overH = null, clickH = null;
function pickTarget(el) {
if (!el || el.nodeType !== 1 || (panel && panel.contains(el))) return null;
let c = el;
for (let i = 0; i < 5 && c; i++) {
const t = (c.tagName || '').toUpperCase();
if (t === 'A') return c;
if (c.getAttribute && c.getAttribute('href')) return c;
try { if (getComputedStyle(c).cursor === 'pointer') return c; } catch (e) {}
c = c.parentElement;
}
return el;
}
function updatePickBtn() { const b = document.getElementById('qcc-auto-pick'); if (!b) return; b.innerHTML = manualPicking ? '✋ 退出选择' : '👆 手动选择'; }
function enterPick() {
if (manualPicking) return;
manualPicking = true;
overH = e => { const el = pickTarget(e.target); if (!el || hoverEl === el) return; if (hoverEl) try { hoverEl.classList.remove('qcc-manual-hover'); } catch (e) {} hoverEl = el; try { el.classList.add('qcc-manual-hover'); } catch (e) {} };
clickH = e => {
if (panel && panel.contains(e.target)) return;
e.preventDefault(); e.stopPropagation();
const el = pickTarget(e.target);
if (!el || selectedEls.includes(el)) return;
selectedEls.push(el);
try { el.classList.add('qcc-pick-selected'); } catch (e) {}
renderList();
setStatus(`👆 手动选择中… 已添加 ${selectedEls.length} 个元素`, 'running');
};
document.addEventListener('mouseover', overH, true);
document.addEventListener('click', clickH, true);
try { document.body.style.cursor = 'crosshair'; } catch (e) {}
updatePickBtn();
setStatus('👆 手动选择:点击页面中的学习内容添加', 'running');
}
function exitPick(clear) {
if (!manualPicking) return;
manualPicking = false;
if (overH) try { document.removeEventListener('mouseover', overH, true); } catch (e) {}
if (clickH) try { document.removeEventListener('click', clickH, true); } catch (e) {}
if (hoverEl) { try { hoverEl.classList.remove('qcc-manual-hover'); } catch (e) {} hoverEl = null; }
try { document.body.style.cursor = ''; } catch (e) {}
updatePickBtn();
if (clear) { const n = selectedEls.length; clearSelected(); setStatus(n > 0 ? `已退出选择,已清空 ${n} 个元素` : '已退出选择', ''); if (n > 0) addLog(`🗑️ 已退出选择,清空 ${n} 个元素`, ''); }
else setStatus(selectedEls.length > 0 ? `已退出选择,保留 ${selectedEls.length} 个元素` : '已退出选择', '');
}
async function expandAll() {
if (expanding) return;
expanding = true;
const st = Date.now(), dl = st + EXPAND_LIMIT;
let total = 0;
const once = new WeakSet();
setStatus('🔍 正在展开目录…', 'running');
addLog('📂 开始展开目录…', '');
try {
for (let loop = 0; loop < EXPAND_LOOPS; loop++) {
if (Date.now() > dl) break;
const arrows = [], seen = new Set();
let items;
try { items = document.querySelectorAll('[class*="sub-menu-title"]'); } catch (e) { items = []; }
for (const el of items) {
if (seen.has(el) || once.has(el) || (panel && panel.contains(el))) continue;
seen.add(el);
const svg = el.querySelector('svg.svg-icon');
if (!svg) continue;
const s = svg.getAttribute('style') || '';
if (s.includes('rotate(180deg)')) continue;
arrows.push({ svg, parent: el });
}
if (!arrows.length) break;
setStatus(`展开中… 剩 ${arrows.length} 个,已点 ${total}`, 'running');
for (const { svg, parent } of arrows) {
if (Date.now() > dl) break;
if (!svg.isConnected) continue;
try { realClick(svg); total++; once.add(parent); } catch (e) {}
await sleep(EXPAND_GAP);
}
await sleep(EXPAND_LOOP_GAP);
}
const el = ((Date.now() - st) / 1000).toFixed(2);
setStatus(`展开完成,共点击 ${total} 个(${el}秒)`, '');
addLog(`📂 展开完成:点击 ${total} 个(${el}s)`, 'ok');
} finally { expanding = false; }
}
function resolveTarget(el) {
let c = el;
for (let i = 0; i < 4 && c; i++) {
const t = (c.tagName || '').toUpperCase();
if (t === 'A') return c;
if (c.getAttribute && c.getAttribute('href')) return c;
try { if (getComputedStyle(c).cursor === 'pointer') return c; } catch (e) {}
c = c.parentElement;
}
return el;
}
function collect() {
const out = new Set();
const MAX = 300;
let list;
try { list = document.querySelectorAll('a.title, .activity-title a, .activity-header a'); } catch (e) { list = []; }
for (const el of list) {
if (out.size >= MAX) return out;
if (panel && panel.contains(el)) continue;
const t = (el.textContent || '').trim().replace(/\s+/g, ' ');
if (!t || t.length > 100) continue;
if (/^(首页|返回|登录|注册|帮助|个人中心|退出|简体中文|全部展开|全部收起|全部|形考任务|终考任务|讨论|目录|简介|直播|公告|学习工具|学习成就|学习成绩|学习分析)$/.test(t)) continue;
let r;
try { r = el.getBoundingClientRect(); } catch (e) { continue; }
if (r.width < 30 || r.height < 10) continue;
if (r.left < 180) continue;
if (r.top < 100) continue;
out.add(el);
}
if (out.size) return out;
try { list = document.querySelectorAll('.text-too-long'); } catch (e) { list = []; }
for (const el of list) {
if (out.size >= MAX) return out;
if (panel && panel.contains(el)) continue;
if (isBold(el)) continue;
const t = (el.textContent || '').trim();
if (!t) continue;
const r = el.getBoundingClientRect();
if (r.width < 20 || r.height < 8) continue;
let s; try { s = getComputedStyle(el); } catch (e) { continue; }
if (s.display === 'none' || s.visibility === 'hidden') continue;
out.add(resolveTarget(el));
}
return out;
}
async function autoPick() {
if (picking) return;
picking = true;
clearSelected();
setStatus('🔍 正在选择…', 'running');
try {
await sleep(200);
let items;
try { items = collect(); }
catch (e) { reportErr('collect 失败', e, true); items = new Set(); }
for (const el of items) {
if (selectedEls.includes(el)) continue;
selectedEls.push(el);
try { el.classList.add('qcc-pick-selected'); } catch (e) {}
}
renderList();
setStatus(selectedEls.length === 0 ? '⚠️ 未找到可点击链接' : `✅ 已选择 ${selectedEls.length} 个可点击链接`, '');
addLog(`🔍 选择完成:${selectedEls.length} 个可点击元素`, selectedEls.length ? 'ok' : '');
} finally { picking = false; }
}
let autoPlayVideo = false, videoSpeed = 2;
function allVideos() {
const out = [], seen = new Set();
const add = vs => { for (const v of vs) if (v.isConnected && !seen.has(v)) { seen.add(v); out.push(v); } };
for (const root of getAllRoots()) { let vs; try { vs = root.querySelectorAll('video'); } catch (e) { continue; } add(vs); }
const docs = [document];
for (let i = 0; i < docs.length; i++) {
let ifs;
try { ifs = docs[i].querySelectorAll('iframe'); } catch (e) { continue; }
for (const f of ifs) { try { const d = f.contentDocument; if (d && !docs.includes(d)) docs.push(d); } catch (e) {} }
}
for (const d of docs) { let vs; try { vs = d.querySelectorAll('video'); } catch (e) { continue; } add(vs); }
return out;
}
const vKey = v => (v.currentSrc || v.src || '') + '::' + (v.getAttribute('data-src') || '');
const snapVKeys = () => new Set(allVideos().map(vKey));
async function waitNewVideo(oldKeys, maxMs) {
const st = Date.now();
while (Date.now() - st < maxMs) {
for (const v of allVideos()) { const k = vKey(v); if (k && !oldKeys.has(k)) return v; }
await sleep(V_POLL);
}
return null;
}
async function waitMeta(v, ms = 8000) {
if (v.readyState >= 1) return true;
return await new Promise(res => {
const t = setTimeout(() => { v.removeEventListener('loadedmetadata', on); res(false); }, ms);
const on = () => { clearTimeout(t); res(true); };
v.addEventListener('loadedmetadata', on, { once: true });
});
}
async function playVideo(video) {
if (!authorized) return false;
await waitMeta(video, 8000);
const realSpeed = videoSpeed;
const fr = () => { try { if (video.playbackRate !== realSpeed) video.playbackRate = realSpeed; } catch (e) {} };
const fm = () => { try { video.muted = true; video.volume = 0; } catch (e) {} };
const pressSpace = () => { try { const ev = new KeyboardEvent('keydown', { key: ' ', code: 'Space', keyCode: 32, bubbles: true, cancelable: true }); video.dispatchEvent(ev); document.body.dispatchEvent(ev); } catch (e) {} };
const forceClickUI = () => {
try {
const r = video.getBoundingClientRect();
const blEl = document.elementFromPoint(r.left + 30, r.bottom - 20);
if (blEl) realClick(blEl);
realClick(video);
if (video.parentElement) realClick(video.parentElement);
} catch (e) {}
};
pressSpace();
forceClickUI();
await sleep(500);
const onPause = () => {
if (!isRunning) return;
setTimeout(() => {
if (video.paused && video.isConnected && isRunning) {
addLog('⚠️ 播放器被强行暂停,尝试唤醒...', 'err');
pressSpace();
forceClickUI();
video.play().catch(() => {});
}
}, 200);
};
const onRC = () => { if (video.playbackRate !== realSpeed) fr(); };
const onVC = () => fm();
let watched = 0, lastCT = 0;
const onTU = () => { const ct = video.currentTime || 0; const d = ct - lastCT; if (d > 0 && d < 2) watched += d; lastCT = ct; };
video.addEventListener('ratechange', onRC);
video.addEventListener('pause', onPause);
video.addEventListener('volumechange', onVC);
video.addEventListener('timeupdate', onTU);
const rt = setInterval(fr, 500);
const kt = setInterval(() => {
if (!isRunning) return;
try { if (video.paused && !video.ended && video.isConnected) { pressSpace(); forceClickUI(); video.play().catch(() => {}); } fm(); fr(); } catch (e) {}
}, 2000);
const cleanup = () => { video.removeEventListener('ratechange', onRC); video.removeEventListener('pause', onPause); video.removeEventListener('volumechange', onVC); video.removeEventListener('timeupdate', onTU); clearInterval(rt); clearInterval(kt); };
try { fm(); fr(); await video.play(); }
catch (e) { pressSpace(); forceClickUI(); await sleep(500); try { await video.play(); } catch (e2) { reportErr('视频 play() 被拒绝', e2, true); } }
await new Promise(res => {
let last = -1, stuck = 0;
const st = Date.now();
const check = () => {
if (!video.isConnected || video.ended) return res();
if (!window.__QCC_LAST_MOUSE || Date.now() - window.__QCC_LAST_MOUSE > 10000) {
window.__QCC_LAST_MOUSE = Date.now();
try { window.focus(); document.dispatchEvent(new MouseEvent('mousemove', { bubbles: true, clientX: 300 + Math.random() * 50, clientY: 300 + Math.random() * 50 })); } catch(e){}
}
const ct = video.currentTime || 0, d = video.duration || 0;
if (d > 0 && isFinite(d) && ct >= d - 0.5) return res();
if (Date.now() - st > V_GRACE) { if (ct > 0 && Math.abs(ct - last) < 0.05) { stuck++; if (stuck > V_STUCK) return res(); } else stuck = 0; }
last = ct; fr(); fm(); setTimeout(check, 500);
};
check();
});
cleanup();
try { recVideo(video, watched); } catch (e) {}
return true;
}
async function autoEnterFirstLesson() {
setResume();
setStatus('📂 检测到章节列表页', 'running');
addLog('📂 进入章节列表处理流程', 'ok');
if (findExpandAllButton()) {
addLog('✅ 发现「全部展开」,点击它', 'ok');
clickExpandAll();
const ok = await waitForCollapseAll(5000);
if (ok) addLog('✅ 目录已展开(出现「全部收起」)', 'ok');
else addLog('⚠️ 等待展开超时,继续尝试', 'err');
} else if (findCollapseAllButton()) {
addLog('ℹ️ 已处于展开态(有「全部收起」),跳过展开', '');
} else {
addLog('⚠️ 未识别到展开/收起按钮', 'err');
}
await sleep(1500);
const entry = findFirstEntry();
if (!entry) {
setStatus('⚠️ 未找到可点击条目', 'err');
addLog('⚠️ 未找到可点击条目,终止', 'err');
clearResume(); isRunning = false; preparing = false; updateRunButtons(); return;
}
const label = (entry.textContent || '').trim().replace(/\s+/g, ' ').slice(0, 30) || '[未命名]';
addLog(`👉 即将进入:${label}`, 'ok');
setStatus(`📂 正在进入:${label}`, 'running');
const oldUrl = location.href;
try { realClick(pickClick(entry) || entry); } catch (e) { reportErr('点击条目失败', e, false); }
await sleep(2000);
if (location.href !== oldUrl) {
addLog('🔄 SPA 跳转成功,2.5 秒后续跑', 'ok');
await sleep(2500);
clearResume(); isRunning = false; preparing = false; selectedEls = []; renderList();
startClicking();
} else {
addLog('⏳ 等待页面刷新后自动续跑', 'ok');
}
}
function highlightClick(el) {
try { document.querySelectorAll('.qcc-clicking').forEach(x => x.classList.remove('qcc-clicking')); } catch (e) {}
try { el.classList.add('qcc-clicking'); } catch (e) {}
currentClickingEl = el;
if (clickHighlightTimer) clearInterval(clickHighlightTimer);
clickHighlightTimer = setInterval(() => {
if (!currentClickingEl) return;
try { if (currentClickingEl.isConnected && !currentClickingEl.classList.contains('qcc-clicking')) currentClickingEl.classList.add('qcc-clicking'); } catch (e) {}
}, 400);
}
async function doClick() {
if (!isRunning || processingVideo) return;
if (!selectedEls.length) { stopClicking(); setStatus('没有可点击的元素', ''); return; }
selectedEls = selectedEls.filter(el => el.isConnected);
if (!selectedEls.length) { stopClicking(); setStatus('所有元素已从页面移除', ''); renderList(); return; }
if (maxClicks > 0 && clickedTotal >= maxClicks) { setStatus(`✅ 已达最大点击次数 ${maxClicks}`, 'ok'); addLog(`✅ 已达最大点击次数 ${maxClicks}`, 'ok'); stopClicking(); return; }
const el = selectedEls[clickIndex % selectedEls.length];
const oldKeys = snapVKeys();
const label = getElText(el);
try { realClick(pickClick(el) || el); highlightClick(el); } catch (e) {}
clickedTotal++; clickIndex++;
clickCountMap.set(el, (clickCountMap.get(el) || 0) + 1);
renderList();
setStatus(`执行中… [${label}] 已点击 ${clickedTotal} 次`, 'running');
addLog(`👆 第 ${clickedTotal} 次:${label}`, '');
updateStats();
if (!authorized || !autoPlayVideo) return;
// ★ 修改2:大幅缩短等待视频出现的时间(V_WAIT 改为了 8 秒,避免空等)
const video = await waitNewVideo(oldKeys, V_WAIT);
if (!video) { addLog('⏳ 未检测到新视频(跳过)', ''); return; }
processingVideo = true; updateRunButtons();
setStatus(`🎬 检测到新视频,正在 ${videoSpeed} 倍速静音播放…`, 'running');
addLog(`🎬 检测到视频,${videoSpeed}x 静音播放中…`, '');
try { await playVideo(video); if (isRunning) setStatus('✅ 视频播放完成', ''); }
catch (e) { reportErr('播放异常', e, false); }
finally { processingVideo = false; updateRunButtons(); }
}
let loopRunning = false;
async function mainLoop() {
if (loopRunning || !isRunning) return;
loopRunning = true;
try {
while (isRunning) {
while (isPaused && isRunning) await sleep(300);
if (!isRunning) break;
await doClick();
if (!isRunning) break;
if (isPaused) continue;
// ★ 修改3:明确生成延迟时间,并在面板打印倒计时日志
const delayMs = randDelay();
const delaySec = (delayMs / 1000).toFixed(1);
addLog(`⏳ 等待 ${delaySec} 秒后继续...`, '');
await waitDelay(delayMs);
}
} finally { loopRunning = false; }
}
async function startClicking() {
if (isRunning) return;
if (manualPicking) exitPick(false);
await sleep(800);
if (isLearningActivityPage()) { addLog('✅ 学习活动页,直接开始刷课', 'ok'); }
else if (isChapterListPage()) { await autoEnterFirstLesson(); return; }
if (!selectedEls.length && !authorized) { setStatus('请先手动选择元素,或授权后自动处理', ''); addLog('⚠️ 无法开始:未授权且未选择任何元素', 'err'); return; }
interruptDelay();
try { if (worker) worker.postMessage({ cmd: 'stop' }); } catch (e) {}
isRunning = true; isPaused = false; processingVideo = false; preparing = false;
clickIndex = 0; clickedTotal = 0;
if (authorized && !selectedEls.length) {
preparing = true; updateRunButtons();
addLog('▶ 开始运行 - 准备阶段', 'ok');
setStatus('⏳ 准备中:展开目录(第 1/2 轮)…', 'running');
await expandAll();
if (!isRunning) { preparing = false; updateRunButtons(); addLog('■ 用户在展开阶段停止', 'err'); return; }
await sleep(EXPAND_ROUND_GAP);
setStatus('⏳ 准备中:展开目录(第 2/2 轮)…', 'running');
await expandAll();
if (!isRunning) { preparing = false; updateRunButtons(); addLog('■ 用户在展开阶段停止', 'err'); return; }
setStatus('⏳ 准备中:自动选择元素…', 'running');
await autoPick();
if (!isRunning) { preparing = false; updateRunButtons(); addLog('■ 用户在选择阶段停止', 'err'); return; }
preparing = false; updateRunButtons();
} else if (authorized && selectedEls.length) {
addLog(`▶ 使用已选择的 ${selectedEls.length} 个元素(跳过展开)`, 'ok');
}
if (!selectedEls.length) { isRunning = false; preparing = false; updateRunButtons(); setStatus('⚠️ 未找到可点击元素', ''); addLog('⚠️ 未找到可点击元素', 'err'); return; }
runStats.start = Date.now(); runStats.vCount = 0; runStats.vSec = 0; runStats.last = 0;
updateStats(); enableKA(); updateRunButtons();
setStatus('开始执行…(随机间隔 5~15 秒)', 'running');
addLog(`▶ 正式开始(${selectedEls.length} 个元素,${authorized && autoPlayVideo ? videoSpeed + 'x 自动播放' : '⚠ 未开启自动播放'})`, authorized && autoPlayVideo ? 'ok' : 'err');
mainLoop();
}
function pauseClicking() { if (!isRunning || isPaused) return; isPaused = true; setStatus('⏸ 已暂停', ''); addLog('⏸ 已暂停', ''); updateRunButtons(); }
function resumeClicking() { if (!isRunning || !isPaused) return; isPaused = false; setStatus('▶ 继续执行…', 'running'); addLog('▶ 继续', 'ok'); updateRunButtons(); }
function stopClicking() {
if (!isRunning) { setStatus('当前未运行', ''); return; }
clearResume(); isRunning = false; isPaused = false; preparing = false;
interruptDelay(); disableKA();
try { if (worker) worker.postMessage({ cmd: 'stop' }); } catch (e) {}
clearClickHighlight();
runStats.last = runStats.start ? (Date.now() - runStats.start) / 1000 : 0;
updateRunButtons();
addLog(`■ 已停止 · 运行 ${fmtHMS(runStats.last)} · 点击 ${clickedTotal} 次 · 视频 ${runStats.vCount} 个(${fmtMS(runStats.vSec)})`, 'err');
setStatus(selectedEls.length ? `已停止(选择列表保留 ${selectedEls.length} 个)` : '已停止', '');
updateStats();
}
async function exportLogs() {
const text = logEntries.map(e => `[${e.time}] ${e.text}`).join('\n');
if (!text) { setStatus('暂无日志可导出', ''); return; }
try { await navigator.clipboard.writeText(text); setStatus(`📋 日志已复制(${logEntries.length} 条)`, 'ok'); addLog('📋 日志已复制到剪贴板', 'ok'); }
catch (e) { const ta = document.createElement('textarea'); ta.value = text; document.body.appendChild(ta); ta.select(); try { document.execCommand('copy'); setStatus('📋 日志已复制', 'ok'); } catch (err) { setStatus('❌ 复制失败', ''); } document.body.removeChild(ta); }
}
function showPortalTip() {
if (document.getElementById('qcc-portal-tip')) return;
const tip = document.createElement('div'); tip.id = 'qcc-portal-tip';
tip.innerHTML = `
1选择你要学习的课程
2点【去学习】进入课程页面
3在课程页面点【▶ 开始】
`;
const s = document.createElement('style');
s.textContent = `#qcc-portal-tip{position:fixed;right:20px;top:100px;width:280px;background:rgba(255,255,255,.95);backdrop-filter:saturate(180%) blur(24px);-webkit-backdrop-filter:saturate(180%) blur(24px);border-radius:16px;box-shadow:0 20px 60px rgba(15,23,42,.22),0 8px 24px rgba(15,23,42,.12),0 0 0 .5px rgba(15,23,42,.08);z-index:2147483646;font-family:-apple-system,BlinkMacSystemFont,"SF Pro Text","Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;color:#1d1d1f;font-size:13px;box-sizing:border-box;overflow:hidden;animation:qccTipIn .35s cubic-bezier(.2,1.2,.4,1)}
@keyframes qccTipIn{from{opacity:0;transform:translateX(20px) scale(.94)}to{opacity:1;transform:translateX(0) scale(1)}}
#qcc-portal-tip *{box-sizing:border-box}
.qcc-tip-header{display:flex;align-items:center;gap:6px;padding:11px 14px;background:linear-gradient(135deg,#6EBBCE 0%,#4f9db8 100%);color:#fff;font-weight:600}
.qcc-tip-icon{font-size:16px;filter:drop-shadow(0 1px 2px rgba(0,0,0,.2))}
.qcc-tip-title{font-size:12.5px;flex:1;letter-spacing:.02em}
.qcc-tip-close{width:22px;height:22px;border:0;background:rgba(255,255,255,.22);color:#fff;border-radius:50%;cursor:pointer;font-size:15px;line-height:1;display:inline-flex;align-items:center;justify-content:center;transition:all .18s;padding:0}
.qcc-tip-close:hover{background:rgba(255,59,48,.75)}
.qcc-tip-body{padding:14px}
.qcc-tip-step{display:flex;align-items:flex-start;gap:10px;padding:8px 0;border-bottom:.5px dashed rgba(15,23,42,.08)}
.qcc-tip-step:last-of-type{border-bottom:0}
.qcc-tip-num{flex:0 0 20px;height:20px;line-height:20px;text-align:center;border-radius:50%;background:linear-gradient(180deg,#34d17a 0%,#12b76a 100%);color:#fff;font-size:11px;font-weight:700;box-shadow:0 2px 6px rgba(18,183,106,.3)}
.qcc-tip-text{flex:1;font-size:12px;color:#3a3a3c;line-height:1.55;padding-top:1px}
.qcc-tip-footer{margin-top:10px;padding:9px 12px;background:linear-gradient(135deg,rgba(52,209,122,.12) 0%,rgba(18,183,106,.12) 100%);border-radius:9px;font-size:11.5px;font-weight:600;color:#0e8f52;text-align:center;letter-spacing:.01em}`;
document.head.appendChild(s); document.body.appendChild(tip);
document.getElementById('qcc-tip-close').addEventListener('click', () => {
tip.style.transition = 'opacity .2s, transform .2s'; tip.style.opacity = '0'; tip.style.transform = 'translateX(10px) scale(.94)';
setTimeout(() => tip.remove(), 200);
});
}
function openDonate() {
if (document.getElementById('qcc-donate-modal')) return;
const m = document.createElement('div'); m.id = 'qcc-donate-modal';
m.innerHTML = `
❤️
感谢您的支持
如果这个脚本帮到了您,
欢迎随意打赏一杯咖啡~
QQ:3365137745
群号:1124065231
授权后可解锁「自动播放视频」和「全自动展开」
🔐 卡密验证
输入卡密后点击「确认」解锁功能
`;
document.body.appendChild(m);
const inp = document.getElementById('qcc-donate-auth-input'); const btn = document.getElementById('qcc-donate-auth-btn'); const msg = document.getElementById('qcc-donate-auth-msg'); const wrap = document.getElementById('qcc-donate-auth');
document.getElementById('qcc-trial-copy').addEventListener('click', async () => {
const b = document.getElementById('qcc-trial-copy'); let ok = false;
try { await navigator.clipboard.writeText(TRIAL); ok = true; }
catch (e) { const ta = document.createElement('textarea'); ta.value = TRIAL; document.body.appendChild(ta); ta.select(); try { document.execCommand('copy'); ok = true; } catch (err) {} document.body.removeChild(ta); }
if (ok) { b.textContent = '已复制'; b.classList.add('copied'); if (inp && !inp.disabled) inp.value = TRIAL; setTimeout(() => { b.textContent = '复制'; b.classList.remove('copied'); }, 1500); }
});
let esc = null;
const close = () => { try { m.remove(); } catch (e) {} if (esc) document.removeEventListener('keydown', esc); };
const sync = () => {
if (authorized) { wrap.classList.add('is-ok'); inp.value = authCode; inp.disabled = true; btn.disabled = true; btn.textContent = '已授权'; msg.textContent = '✅ 功能已解锁'; msg.className = 'qcc-auth-msg ok'; }
else { wrap.classList.remove('is-ok'); inp.disabled = false; btn.disabled = false; btn.textContent = '确认'; msg.textContent = authCode ? '⚠️ 上次验证未通过,请重新验证' : '输入卡密后点击「确认」解锁功能'; msg.className = 'qcc-auth-msg'; if (authCode) inp.value = authCode; }
};
const tryAuth = async () => {
btn.disabled = true; btn.textContent = '验证中…';
const r = await doVerify(inp.value);
btn.disabled = false;
if (r.ok) { sync(); setStatus(r.msg, ''); addLog('🔐 卡密验证成功', 'ok'); setTimeout(close, 1200); }
else { msg.textContent = r.msg; msg.className = 'qcc-auth-msg err'; setStatus(r.msg.replace(/[✅❌⚠️]\s*/g, ''), ''); addLog('🔐 ' + r.msg.replace(/[✅❌⚠️]\s*/g, ''), 'err'); wrap.classList.remove('qcc-shake'); void wrap.offsetWidth; wrap.classList.add('qcc-shake'); try { inp.focus(); inp.select(); } catch (e) {} btn.textContent = '确认'; }
};
btn.addEventListener('click', tryAuth);
inp.addEventListener('keydown', e => { if (e.key === 'Enter') { e.preventDefault(); tryAuth(); } });
m.addEventListener('click', e => { if (e.target === m) close(); });
document.getElementById('qcc-donate-close').addEventListener('click', close);
esc = e => { if (e.key === 'Escape') close(); };
document.addEventListener('keydown', esc);
sync();
if (!authorized) setTimeout(() => { try { inp.focus(); } catch (e) {} }, 120);
}
function buildPanel() {
if (document.getElementById('quick-custom-clicker')) return;
loadCfg(); loadTheme();
panel = document.createElement('div'); panel.id = 'quick-custom-clicker';
panel.innerHTML = `
▶️ 执行
2~12s 随机学习
等待操作
已选择学习的目标的数量0个
运行 00:00:00视频 0分钟 (0个)点击 0次
`;
const s = document.createElement('style');
s.textContent = `
#quick-custom-clicker{position:fixed;top:100px;right:20px;width:280px;background:rgba(255,255,255,.72);backdrop-filter:saturate(180%) blur(24px);-webkit-backdrop-filter:saturate(180%) blur(24px);border-radius:18px;box-shadow:0 20px 60px rgba(15,23,42,.18),0 8px 24px rgba(15,23,42,.10),0 1px 0 rgba(255,255,255,.6) inset,0 0 0 .5px rgba(15,23,42,.08);z-index:2147483647;font-family:-apple-system,BlinkMacSystemFont,"SF Pro Text","Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;color:#1d1d1f;box-sizing:border-box;overflow:hidden;font-size:12px;transition:width .28s cubic-bezier(.4,0,.2,1),background .25s;color-scheme:light;-webkit-font-smoothing:antialiased;letter-spacing:-.01em}
#quick-custom-clicker *{box-sizing:border-box}
.qcc-header{display:flex;justify-content:space-between;align-items:center;padding:10px 12px;background:#6EBBCE;color:#fff;cursor:move;user-select:none;border-bottom:.5px solid rgba(15,23,42,.08);transition:background .25s}
.qcc-header-left{display:flex;align-items:center;gap:6px}
.qcc-logo{font-size:14px;filter:drop-shadow(0 1px 1.5px rgba(0,0,0,.2))}
.qcc-title-text{font-size:13px;font-weight:600;color:#fff}
.qcc-badge{font-size:9px;background:rgba(255,255,255,.22);color:#fff;padding:2px 6px;border-radius:6px;font-weight:600}
.qcc-header-right{display:flex;gap:6px}
.qcc-icon-btn{width:22px;height:22px;border:0;background:rgba(255,255,255,.22);color:#fff;border-radius:50%;cursor:pointer;font-size:14px;line-height:1;display:inline-flex;align-items:center;justify-content:center;transition:all .18s;padding:0;transform:translateY(-.5px)}
.qcc-icon-btn:hover{background:rgba(255,255,255,.38)}
.qcc-icon-btn#qcc-close:hover{background:rgba(255,59,48,.75)}
.qcc-body{padding:12px;max-height:720px;overflow-y:auto;transition:max-height .28s,padding .28s,opacity .2s}
.qcc-body.collapsed{max-height:0;padding-top:0;padding-bottom:0;opacity:0;overflow:hidden}
.qcc-body::-webkit-scrollbar{width:6px}
.qcc-body::-webkit-scrollbar-thumb{background:rgba(15,23,42,.12);border-radius:3px}
.qcc-section{margin-bottom:14px}
.qcc-section-title{font-size:10px;font-weight:600;color:#8e8e93;letter-spacing:.04em;text-transform:uppercase;margin-bottom:8px;padding-left:2px;display:flex;justify-content:space-between;align-items:center;gap:6px}
.qcc-auth-tip{font-size:9px;color:#1d1d1f;font-weight:500;margin-left:auto;white-space:nowrap;letter-spacing:0;text-transform:none}
.qcc-auth-time{font-size:9.5px;font-weight:600;color:#fff;background:linear-gradient(180deg,#34d17a 0%,#12b76a 100%);padding:0 10px;height:20px;line-height:20px;border-radius:10px;font-variant-numeric:tabular-nums;white-space:nowrap;display:inline-flex;align-items:center;box-shadow:0 2px 6px rgba(18,183,106,.28);flex:0 0 auto}
.qcc-auth-time.warn{background:linear-gradient(180deg,#ffb340 0%,#f79009 100%);box-shadow:0 2px 6px rgba(247,144,9,.28)}
.qcc-auth-time.err{background:linear-gradient(180deg,#ff6b61 0%,#ff3b30 100%);box-shadow:0 2px 6px rgba(255,59,48,.28)}
.qcc-theme-mini{display:inline-flex;align-items:center;gap:3px;flex:0 0 auto;margin-left:8px}
.qcc-theme-mini .qcc-theme-label{font-size:9px;font-weight:500;color:#8e8e93;cursor:pointer;user-select:none;transition:color .2s;letter-spacing:0}
.qcc-theme-mini .qcc-theme-label.active{color:#4f6ef7;font-weight:600}
.qcc-theme-switch-mini{width:26px !important;height:14px !important;flex:0 0 26px !important}
.qcc-theme-switch-mini .qcc-switch-slider{border-radius:7px}
.qcc-theme-switch-mini .qcc-switch-slider:before{height:10px;width:10px;left:2px;bottom:2px}
.qcc-theme-switch-mini input:checked + .qcc-switch-slider:before{transform:translateX(12px)}
.qcc-field-block{margin-bottom:8px}
.qcc-field-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px}
.qcc-field-head label,.qcc-field-row label,.qcc-double-item label{font-size:11px;color:#3a3a3c;font-weight:500}
.qcc-field-row{display:flex;justify-content:space-between;align-items:center;padding:6px 0;border-bottom:.5px solid rgba(15,23,42,.06);gap:8px}
.qcc-field-compact{padding:6px 0}
.qcc-interval-value{font-size:10px;font-weight:600;color:#4f6ef7;background:rgba(79,110,247,.10);padding:3px 8px;border-radius:6px;flex:0 0 auto}
.qcc-row-with-btn{gap:6px}
.qcc-inline-btn{margin-left:auto;flex:0 0 auto;height:24px;padding:0 10px;border:0;border-radius:8px;background:linear-gradient(180deg,#5b78ff 0%,#4f6ef7 100%);color:#fff;font-size:10.5px;font-weight:500;cursor:pointer;font-family:inherit;display:inline-flex;align-items:center;gap:4px;transition:all .18s;white-space:nowrap;box-shadow:0 3px 10px rgba(79,110,247,.28)}
.qcc-inline-btn:hover{background:linear-gradient(180deg,#6b85ff 0%,#5b78ff 100%);transform:translateY(-1px)}
.qcc-inline-btn:active{transform:scale(.97)}
.qcc-inline-btn .qcc-btn-icon{font-size:11px;line-height:1}
.qcc-num-input{flex:0 0 42px !important;width:42px !important;height:20px !important;min-height:20px !important;max-height:20px !important;margin:0 !important;border:0 !important;background:linear-gradient(180deg,#34d17a 0%,#12b76a 100%) !important;color:#fff !important;border-radius:10px !important;padding:0 6px !important;font-size:10px !important;font-weight:600 !important;line-height:20px !important;outline:none !important;box-shadow:0 2px 6px rgba(18,183,106,.28) !important;font-family:inherit !important;text-align:center !important;transition:background .18s,box-shadow .18s !important;-moz-appearance:textfield !important;font-variant-numeric:tabular-nums !important;display:inline-block !important;vertical-align:middle !important}
.qcc-num-input::-webkit-outer-spin-button,.qcc-num-input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}
.qcc-num-input:hover:not(:disabled){background:linear-gradient(180deg,#43dc8a 0%,#22c876 100%) !important}
.qcc-num-input:focus{box-shadow:0 0 0 3px rgba(18,183,106,.25) !important}
.qcc-num-input:disabled{opacity:.35;filter:grayscale(1);cursor:not-allowed}
.qcc-field-double{gap:10px}
.qcc-double-item{flex:1;display:flex;align-items:center;justify-content:space-between;gap:6px;min-width:0;line-height:20px}
.qcc-double-item label{white-space:nowrap;flex:0 0 auto;height:20px;display:inline-flex;align-items:center}
.qcc-double-item .qcc-num-input{flex:0 0 42px !important;width:42px !important;line-height:20px !important}
.qcc-double-item .qcc-switch{flex:0 0 36px}
.qcc-btn{width:100%;border:0;border-radius:10px;padding:0 12px;height:24px;cursor:pointer;font-size:10.5px;font-weight:500;display:flex;align-items:center;justify-content:center;gap:6px;transition:all .18s;font-family:inherit;line-height:1}
.qcc-btn-icon{font-size:11px;line-height:1}
.qcc-btn:disabled{opacity:.4;cursor:not-allowed}
.qcc-btn:not(:disabled):active{transform:scale(.97)}
.qcc-btn-success{background:linear-gradient(180deg,#34d17a 0%,#12b76a 100%);color:#0a3d2e;box-shadow:0 4px 14px rgba(18,183,106,.3)}
#quick-custom-clicker.qcc-dark .qcc-btn-success{color:#0a3d2e}
.qcc-btn-danger{background:rgba(255,59,48,.08);color:#ff3b30}
.qcc-btn-danger:not(:disabled):hover{background:rgba(255,59,48,.15)}
.qcc-btn-ghost{background:rgba(15,23,42,.05);color:#3a3a3c}
.qcc-btn-ghost:not(:disabled):hover{background:rgba(15,23,42,.09)}
.qcc-btn-donate{background:linear-gradient(180deg,#ff7aa2 0%,#ff4d7d 100%);color:#fff;box-shadow:0 4px 14px rgba(255,77,125,.3)}
.qcc-btn-sm{padding:0 8px;height:24px;font-size:10.5px;margin-top:6px;gap:4px}
.qcc-btn-sm .qcc-btn-icon{font-size:11px}
.qcc-btn-group{display:flex;gap:6px}
.qcc-btn-group-sm{margin-top:6px}
.qcc-btn-group-sm .qcc-btn-sm{margin-top:0;flex:1}
.qcc-status{display:flex;align-items:center;gap:8px;padding:9px 12px;background:rgba(15,23,42,.04);border-radius:10px;font-size:10.5px;color:#3a3a3c;margin-bottom:12px;line-height:1.35}
.qcc-status-dot{width:7px;height:7px;border-radius:50%;background:#8e8e93;flex-shrink:0}
.qcc-status.running .qcc-status-dot{background:#12b76a;box-shadow:0 0 0 3px rgba(18,183,106,.18);animation:qcc-pulse 1.4s infinite}
@keyframes qcc-pulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.55;transform:scale(1.15)}}
.qcc-list-count{background:rgba(79,110,247,.12);color:#4f6ef7;padding:2px 8px;border-radius:6px;font-size:9.5px;font-weight:600;min-width:20px;text-align:center}
.qcc-pick-selected{outline:3px solid #ff6b00 !important;outline-offset:2px !important;background:linear-gradient(90deg,rgba(255,196,0,.55) 0%,rgba(255,140,0,.45) 100%) !important;border-radius:6px;box-shadow:0 0 0 2px rgba(255,107,0,.2) inset,0 3px 10px rgba(255,107,0,.3) !important;color:#7a3a00 !important;transition:all .15s}
.qcc-pick-selected *{color:#7a3a00 !important}
.qcc-clicking{outline:3px solid #12b76a !important;outline-offset:2px !important;background:linear-gradient(90deg,rgba(52,209,122,.6) 0%,rgba(18,183,106,.5) 100%) !important;border-radius:6px;box-shadow:0 0 0 2px rgba(18,183,106,.25) inset,0 3px 10px rgba(18,183,106,.35) !important;color:#0e5c34 !important;transition:all .15s}
.qcc-clicking *{color:#0e5c34 !important}
.qcc-manual-hover{outline:2px dashed #4f6ef7 !important;outline-offset:2px !important;background:rgba(79,110,247,.08) !important;cursor:crosshair !important}
.qcc-switch{position:relative;width:36px;height:20px;flex:0 0 36px;display:inline-block}
.qcc-switch input{opacity:0;width:0;height:0}
.qcc-switch-slider{position:absolute;cursor:pointer;inset:0;background:rgba(15,23,42,.12);border-radius:10px;transition:.28s}
.qcc-switch-slider:before{position:absolute;content:'';height:16px;width:16px;left:2px;bottom:2px;background:#fff;border-radius:50%;transition:.28s;box-shadow:0 1px 3px rgba(0,0,0,.18)}
.qcc-switch input:checked + .qcc-switch-slider{background:linear-gradient(180deg,#34d17a 0%,#12b76a 100%)}
.qcc-switch input:checked + .qcc-switch-slider:before{transform:translateX(16px)}
.qcc-switch input:disabled + .qcc-switch-slider{opacity:.35;cursor:not-allowed}
.qcc-speed-btns{display:flex;gap:4px;margin-top:5px}
.qcc-speed-btn{flex:1;height:24px;border-radius:8px;border:.5px solid rgba(15,23,42,.1);background:rgba(255,255,255,.6);color:#6b7280;font-size:10.5px;font-weight:600;cursor:pointer;padding:0;font-family:inherit;transition:all .18s}
.qcc-speed-btn:hover{background:rgba(255,255,255,.95);color:#4f6ef7}
.qcc-speed-btn.active{background:linear-gradient(180deg,#5b78ff 0%,#4f6ef7 100%);border-color:transparent;color:#fff}
.qcc-speed-value{font-size:10px;font-weight:600;color:#4f6ef7;background:rgba(79,110,247,.10);padding:3px 8px;border-radius:6px;min-width:36px;text-align:center}
#qcc-speed-block.qcc-hidden{display:none}
.qcc-title-short{display:none}
#quick-custom-clicker.qcc-collapsed{width:105px}
#quick-custom-clicker.qcc-collapsed .qcc-header{padding:9px 9px 9px 12px;border-bottom:0}
#quick-custom-clicker.qcc-collapsed .qcc-logo,#quick-custom-clicker.qcc-collapsed .qcc-badge,#quick-custom-clicker.qcc-collapsed .qcc-title-full{display:none}
#quick-custom-clicker.qcc-collapsed .qcc-title-short{display:inline;font-size:13px;font-weight:600;color:#fff}
#quick-custom-clicker.qcc-collapsed .qcc-header-left{gap:0;flex:1;justify-content:flex-start}
#quick-custom-clicker.qcc-collapsed .qcc-icon-btn{width:26px;height:26px;font-size:18px;background:rgba(255,255,255,.3)}
#quick-custom-clicker.qcc-collapsed #qcc-close{display:none}
.qcc-log-box{background:rgba(15,23,42,.04);border-radius:10px;padding:8px 10px;margin-bottom:12px}
.qcc-picked-bar{display:flex;justify-content:space-between;align-items:center;font-size:10px;color:#8e8e93;font-weight:600;padding-bottom:6px;margin-bottom:6px;border-bottom:.5px dashed rgba(15,23,42,.1)}
.qcc-log-header{display:flex;justify-content:space-between;align-items:center;font-size:10px;color:#8e8e93;font-weight:600;margin-bottom:6px}
.qcc-log-actions{display:flex;gap:4px}
.qcc-log-reset{border:0;background:transparent;color:#8e8e93;font-size:9.5px;padding:2px 6px;border-radius:4px;cursor:pointer;font-family:inherit;transition:all .2s}
.qcc-log-reset:hover{background:rgba(79,110,247,.1);color:#4f6ef7}
.qcc-stat-compact{display:flex;flex-wrap:wrap;gap:4px 12px;font-size:10px;color:#6b7280;padding:4px 0;margin-bottom:6px;border-bottom:.5px dashed rgba(15,23,42,.1)}
.qcc-stat-compact b{font-weight:600;color:#1d1d1f;font-variant-numeric:tabular-nums}
.qcc-log-list{max-height:60px;overflow-y:auto;font-size:10px;line-height:1.5}
.qcc-log-list::-webkit-scrollbar{width:10px}
.qcc-log-list::-webkit-scrollbar-thumb{background:rgba(120,120,120,.55);border-radius:5px;border:2px solid transparent;background-clip:content-box}
.qcc-log-item{display:flex;gap:6px;padding:2px 0;border-bottom:.5px solid rgba(15,23,42,.04)}
.qcc-log-time{color:#a1a1a6;flex-shrink:0;font-variant-numeric:tabular-nums}
.qcc-log-text{color:#3a3a3c;word-break:break-all}
.qcc-log-item.ok .qcc-log-text{color:#0e8f52}
.qcc-log-item.err .qcc-log-text{color:#ff3b30}
.qcc-log-empty{color:#a1a1a6;text-align:center;padding:8px 0;font-size:10px}
#qcc-donate-modal{position:fixed;inset:0;z-index:2147483647;background:rgba(15,23,42,.38);backdrop-filter:blur(6px);display:flex;align-items:center;justify-content:center;font-family:inherit;animation:qcc-fade-in .2s}
@keyframes qcc-fade-in{from{opacity:0}to{opacity:1}}
.qcc-donate-card{width:290px;background:rgba(255,255,255,.94);backdrop-filter:saturate(180%) blur(24px);border-radius:18px;padding:20px 18px 16px;text-align:center;box-shadow:0 24px 70px rgba(15,23,42,.28);animation:qcc-pop-in .26s cubic-bezier(.2,1.2,.4,1)}
@keyframes qcc-pop-in{from{transform:scale(.9);opacity:0}to{transform:scale(1);opacity:1}}
.qcc-donate-emoji{font-size:34px;margin-bottom:8px}
.qcc-donate-title{font-size:15px;font-weight:600;color:#1d1d1f;margin-bottom:6px}
.qcc-donate-desc{font-size:11px;color:#6b7280;line-height:1.6;margin-bottom:12px}
.qcc-donate-links{display:flex;justify-content:center;gap:6px;flex-wrap:wrap;margin-bottom:6px}
.qcc-donate-links .qcc-donate-qq{margin-bottom:0}
.qcc-donate-qq{display:inline-block;font-size:12px;font-weight:600;color:#4f6ef7;background:rgba(79,110,247,.10);padding:6px 12px;border-radius:8px;margin-bottom:6px;user-select:text}
.qcc-donate-note{font-size:10px;color:#a1a1a6;margin-bottom:12px}
.qcc-trial-card{text-align:left;background:linear-gradient(135deg,rgba(255,181,64,.12) 0%,rgba(255,120,70,.12) 100%);border:.5px solid rgba(255,149,0,.3);border-radius:10px;padding:9px 10px;margin-bottom:12px}
.qcc-trial-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px}
.qcc-trial-label{font-size:10px;font-weight:600;color:#b25e00}
.qcc-trial-copy{height:20px;padding:0 8px;border:0;border-radius:6px;background:linear-gradient(180deg,#ffb340 0%,#f79009 100%);color:#fff;font-size:10px;font-weight:600;cursor:pointer;font-family:inherit;box-shadow:0 2px 6px rgba(247,144,9,.3);transition:all .18s;display:inline-flex;align-items:center}
.qcc-trial-copy.copied{background:linear-gradient(180deg,#34d17a 0%,#12b76a 100%)}
.qcc-trial-code{font-size:12px;font-weight:700;color:#1d1d1f;font-family:ui-monospace,Menlo,monospace;letter-spacing:.1em;word-break:break-all;background:rgba(255,255,255,.75);padding:6px 9px;border-radius:6px;user-select:text;text-align:center}
.qcc-donate-auth{text-align:left;background:rgba(255,149,0,.08);border:.5px solid rgba(255,149,0,.22);border-radius:10px;padding:10px;margin-bottom:12px;transition:.2s}
.qcc-donate-auth.is-ok{background:rgba(18,183,106,.08);border-color:rgba(18,183,106,.25)}
.qcc-donate-auth.qcc-shake{animation:qcc-shake .36s}
@keyframes qcc-shake{10%,90%{transform:translateX(-1.5px)}20%,80%{transform:translateX(3px)}30%,50%,70%{transform:translateX(-5px)}40%,60%{transform:translateX(5px)}}
.qcc-donate-auth-title{font-size:10px;font-weight:600;color:#b25e00;margin-bottom:7px}
.qcc-donate-auth.is-ok .qcc-donate-auth-title{color:#0e8f52}
.qcc-donate-auth-row{display:flex;gap:6px}
.qcc-auth-input{flex:1;min-width:0;height:26px;border:.5px solid rgba(15,23,42,.12);background:rgba(255,255,255,.85);border-radius:7px;padding:0 8px;font-size:10.5px;outline:none;font-family:inherit}
.qcc-auth-input:focus{border-color:rgba(79,110,247,.5);box-shadow:0 0 0 3px rgba(79,110,247,.12)}
.qcc-auth-input:disabled{background:rgba(15,23,42,.04);color:#6b7280}
.qcc-auth-btn{height:26px;padding:0 12px;border:0;border-radius:7px;background:linear-gradient(180deg,#ffb340 0%,#f79009 100%);color:#fff;font-size:10.5px;font-weight:600;cursor:pointer;font-family:inherit;box-shadow:0 2px 8px rgba(247,144,9,.3)}
.qcc-auth-btn:disabled{opacity:.6;background:rgba(18,183,106,.75)}
.qcc-auth-msg{font-size:9.5px;color:#8e8e93;margin-top:5px;line-height:1.45}
.qcc-auth-msg.err{color:#ff3b30}
.qcc-auth-msg.ok{color:#12b76a}
.qcc-donate-close{width:100%;border:0;border-radius:10px;padding:9px 12px;background:rgba(15,23,42,.06);color:#3a3a3c;font-size:11.5px;cursor:pointer;font-family:inherit}
.qcc-donate-close:hover{background:rgba(15,23,42,.11)}
#quick-custom-clicker.qcc-dark{background:rgba(30,30,32,.88);color:#f5f5f7;color-scheme:dark;box-shadow:0 20px 60px rgba(0,0,0,.5),0 8px 24px rgba(0,0,0,.3)}
#quick-custom-clicker.qcc-dark .qcc-header{background:#3a4a55}
#quick-custom-clicker.qcc-dark .qcc-section-title{color:#8e8e93}
#quick-custom-clicker.qcc-dark .qcc-field-head label,#quick-custom-clicker.qcc-dark .qcc-field-row label,#quick-custom-clicker.qcc-dark .qcc-double-item label{color:#c7c7cc}
#quick-custom-clicker.qcc-dark .qcc-auth-tip{color:#f5f5f7}
#quick-custom-clicker.qcc-dark .qcc-theme-mini .qcc-theme-label{color:#8e8e93}
#quick-custom-clicker.qcc-dark .qcc-theme-mini .qcc-theme-label.active{color:#8ba0ff}
#quick-custom-clicker.qcc-dark .qcc-status{background:rgba(255,255,255,.06);color:#c7c7cc}
#quick-custom-clicker.qcc-dark .qcc-log-box{background:rgba(255,255,255,.05)}
#quick-custom-clicker.qcc-dark .qcc-picked-bar{color:#8e8e93;border-bottom-color:rgba(255,255,255,.1)}
#quick-custom-clicker.qcc-dark .qcc-list-count{background:rgba(79,110,247,.25);color:#8ba0ff}
#quick-custom-clicker.qcc-dark .qcc-stat-compact{color:#8e8e93;border-bottom-color:rgba(255,255,255,.1)}
#quick-custom-clicker.qcc-dark .qcc-stat-compact b{color:#f5f5f7}
#quick-custom-clicker.qcc-dark .qcc-log-text{color:#c7c7cc}
#quick-custom-clicker.qcc-dark .qcc-log-time{color:#8e8e93}
#quick-custom-clicker.qcc-dark .qcc-log-item{border-bottom-color:rgba(255,255,255,.05)}
#quick-custom-clicker.qcc-dark .qcc-btn-ghost{background:rgba(255,255,255,.08);color:#c7c7cc}
#quick-custom-clicker.qcc-dark .qcc-speed-btn{background:rgba(255,255,255,.08);color:#c7c7cc;border-color:rgba(255,255,255,.12)}
#quick-custom-clicker.qcc-dark .qcc-speed-btn.active{background:linear-gradient(180deg,#6b85ff 0%,#5b78ff 100%);color:#fff;border-color:transparent}
`;
document.head.appendChild(s); document.body.appendChild(panel);
applyScale(); applyTheme();
statusEl = document.getElementById('qcc-status'); statusTextEl = document.getElementById('qcc-status-text'); listCountEl = document.getElementById('qcc-list-count'); logListEl = document.getElementById('qcc-log-list');
statRT = document.getElementById('qcc-stat-runtime'); statV = document.getElementById('qcc-stat-video'); statVC = document.getElementById('qcc-stat-videos'); statC = document.getElementById('qcc-stat-clicks');
if (logTimer) clearInterval(logTimer);
logTimer = setInterval(() => { updateStats(); updateAuthTime(); }, 1000);
renderLog(); updateStats(); updateAuthTime();
document.getElementById('qcc-log-reset').addEventListener('click', () => { resetStats(); setStatus('日志与统计已重置', ''); });
document.getElementById('qcc-log-export').addEventListener('click', exportLogs);
const themeToggle = document.getElementById('qcc-theme-toggle');
if (themeToggle) { themeToggle.checked = isDarkTheme; themeToggle.addEventListener('change', () => { isDarkTheme = themeToggle.checked; applyTheme(); }); }
panel.querySelectorAll('.qcc-theme-label').forEach(l => { l.addEventListener('click', () => { isDarkTheme = (l.dataset.theme === 'dark'); applyTheme(); }); });
const av = document.getElementById('qcc-auto-video'); const sb = document.getElementById('qcc-speed-block'); const sv = document.getElementById('qcc-speed-value');
const speedBtns = panel.querySelectorAll('.qcc-speed-btn'); const mci = document.getElementById('qcc-max-clicks');
mci.value = String(maxClicks || 0);
mci.addEventListener('input', () => { const n = parseInt(mci.value, 10); maxClicks = (isNaN(n) || n < 0) ? 0 : n; saveCfg(); });
mci.addEventListener('blur', () => { mci.value = String(maxClicks || 0); });
syncAuthUI = function () {
updatePickBtn();
const avEl = document.getElementById('qcc-auto-video'); const mcEl = document.getElementById('qcc-max-clicks');
const sBlk = document.getElementById('qcc-speed-block'); const eT = document.getElementById('qcc-execute-title');
const aL = document.getElementById('qcc-auto-video-label'); const aTip = document.getElementById('qcc-auth-tip');
if (authorized) {
loadCfg();
if (avEl) { avEl.disabled = false; avEl.checked = autoPlayVideo; }
if (mcEl) { mcEl.disabled = false; mcEl.value = String(maxClicks || 0); }
if (aL) aL.textContent = '自动播放';
if (sBlk) sBlk.classList.toggle('qcc-hidden', !autoPlayVideo);
if (eT) eT.innerHTML = '▶️ 执行 · 点→开始→ [全自动开始你的学习]';
if (aTip) aTip.style.display = 'none';
if (sv) sv.textContent = videoSpeed + 'x';
speedBtns.forEach(b => b.classList.toggle('active', parseInt(b.dataset.speed, 10) === videoSpeed));
} else {
autoPlayVideo = false; videoSpeed = 1;
if (avEl) { avEl.checked = false; avEl.disabled = true; }
if (mcEl) { mcEl.value = '0'; mcEl.disabled = true; maxClicks = 0; }
if (aL) aL.textContent = '自动播放 🔒';
if (sBlk) sBlk.classList.add('qcc-hidden');
if (eT) eT.innerHTML = '▶️ 执行 · 手动模式';
if (aTip) aTip.style.display = '';
if (sv) sv.textContent = '1x';
speedBtns.forEach(b => b.classList.remove('active'));
}
updateAuthTime();
if (authorized && manualPicking) exitPick(false);
};
function syncVUI() { av.checked = autoPlayVideo; sb.classList.toggle('qcc-hidden', !authorized || !autoPlayVideo); sv.textContent = videoSpeed + 'x'; speedBtns.forEach(b => b.classList.toggle('active', parseInt(b.dataset.speed, 10) === videoSpeed)); updateRunButtons(); }
function flashDonate() { const b = document.getElementById('qcc-donate'); if (!b) return; b.style.transition = 'all .2s'; b.style.transform = 'scale(1.08)'; b.style.boxShadow = '0 0 0 4px rgba(255,77,125,.3)'; setTimeout(() => { b.style.transform = 'scale(1)'; b.style.boxShadow = ''; }, 400); }
av.addEventListener('click', e => {
if (!authorized) { e.preventDefault(); av.checked = false; autoPlayVideo = false; syncVUI(); setStatus('🔒 请点击【❤️ 捐赠 / 授权】解锁「自动播放视频」', ''); flashDonate(); return; }
autoPlayVideo = av.checked; syncVUI(); saveCfg(); setStatus(`自动播放视频: ${autoPlayVideo ? '开启' : '关闭'}`, ''); addLog(`⚙️ 自动播放视频 ${autoPlayVideo ? '开启' : '关闭'}`, '');
});
speedBtns.forEach(b => b.addEventListener('click', () => {
if (!authorized) { setStatus('🔒 请先授权', ''); return; }
videoSpeed = parseInt(b.dataset.speed, 10) || 2; syncVUI(); saveCfg(); setStatus(`视频倍速已设为 ${videoSpeed}x`, ''); addLog(`⚙️ 视频倍速 ${videoSpeed}x`, '');
}));
syncVUI();
document.getElementById('qcc-auto-pick').addEventListener('click', () => { if (manualPicking) exitPick(true); else enterPick(); });
document.getElementById('qcc-donate').addEventListener('click', openDonate);
document.getElementById('qcc-start').addEventListener('click', startClicking);
document.getElementById('qcc-stop').addEventListener('click', stopClicking);
document.getElementById('qcc-pause').addEventListener('click', () => { if (isPaused) resumeClicking(); else pauseClicking(); });
function toggleCollapse() { const b = document.getElementById('qcc-body'); const c = b.classList.toggle('collapsed'); panel.classList.toggle('qcc-collapsed', c); document.getElementById('qcc-min').textContent = c ? '+' : '−'; }
document.getElementById('qcc-min').addEventListener('click', e => { e.stopPropagation(); toggleCollapse(); });
document.getElementById('qcc-close').addEventListener('click', () => {
stopHB(); if (manualPicking) exitPick(false); stopClicking();
if (logTimer) { clearInterval(logTimer); logTimer = null; }
const dm = document.getElementById('qcc-donate-modal'); if (dm) dm.remove();
panel.remove(); syncAuthUI = null;
});
(function drag() {
const ed = document.getElementById('qcc-drag'); let dr = false, sx = 0, sy = 0, bl = 0, bt = 0;
ed.addEventListener('mousedown', e => {
if (e.target.closest('.qcc-icon-btn')) return;
dr = true; const z = parseFloat(panel.style.zoom) || 1; sx = e.clientX; sy = e.clientY; const r = panel.getBoundingClientRect();
bl = r.left / z; bt = r.top / z; panel.style.right = 'auto'; panel.style.left = bl + 'px'; panel.style.top = bt + 'px'; e.preventDefault();
});
document.addEventListener('mousemove', e => {
if (!dr) return; const z = parseFloat(panel.style.zoom) || 1;
const nx = Math.max(0, Math.min(innerWidth / z - panel.offsetWidth, bl + (e.clientX - sx) / z));
const ny = Math.max(0, Math.min(innerHeight / z - 40, bt + (e.clientY - sy) / z));
panel.style.left = nx + 'px'; panel.style.top = ny + 'px';
});
document.addEventListener('mouseup', () => { dr = false; });
})();
document.addEventListener('keydown', e => { if (e.key === 'Escape') { if (manualPicking) { exitPick(false); return; } if (isRunning) stopClicking(); } });
updatePickBtn(); renderList(); updateRunButtons();
if (syncAuthUI) try { syncAuthUI(); } catch (e) {}
(async () => {
await loadAuth(); updatePickBtn();
if (syncAuthUI) try { syncAuthUI(); } catch (e) {}
setStatus(authorized ? '✅ 已授权,点开始自动运行' : (authCode ? '⚠️ 上次验证未通过' : '等待操作'), '');
if (authorized && getResume()) { addLog('🔄 检测到续跑标志,2.5 秒后自动开始', 'ok'); await sleep(2500); clearResume(); startClicking(); }
})();
}
function safeBuild() {
try {
if (!document.body) { setTimeout(safeBuild, 50); return; }
if (location.hostname === 'menhu.pt.ouchn.cn') { showPortalTip(); return; }
buildPanel();
} catch (e) { reportErr('面板构建失败', e, false); }
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', safeBuild, { once: true });
else safeBuild();
window.addEventListener('resize', () => { try { applyScale(); } catch (e) {} });
})();