// ==UserScript==
// @name 国开自动刷课+次数和时长(试用卡版)
// @namespace https://scriptcat.org/
// @version 5.0.0
// @description 目录展开与选择一栏|轮流循环点击|暂停/继续|2/4/6/8/10倍速|静音播放完再继续|10分钟+超级保活|苹果风格UI|ESC停止|后台也继续运行|自建授权服务器|运行日志与统计|自动缩放
// @author You
// @match *://lms.ouchn.cn/*
// @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 CFG = {
AUTH_SERVER: 'https://aitwo.icu',
HEARTBEAT_MS: 50000, HEARTBEAT_FAIL_LIMIT: 3,
RANDOM_MIN_SEC: 2, RANDOM_MAX_SEC: 12,
VIDEO_WAIT_MS: 15000, VIDEO_POLL_MS: 300,
EXPAND_HARD_LIMIT_MS: 5800, EXPAND_MAX_LOOPS: 50,
EXPAND_CLICK_GAP_MS: 25, EXPAND_LOOP_GAP_MS: 100,
ROOTS_THROTTLE_MS: 300, LOG_MAX: 80,
SPEED_OPTIONS: [2, 4, 6, 8, 10],
VIDEO_PLAY_GRACE_MS: 15000, VIDEO_STUCK_LIMIT: 30,
TRIAL_CODE: 'CS-2VJY5A5T7Q'
};
const STORAGE_KEYS = { AUTH: 'qcc_auth_code_v3', EXPIRE: 'qcc_auth_expire_v3', DEVICE: 'qcc_device_id', CFG: 'qcc_cfg_v2' };
let authCode = '', authorized = false;
let __syncAuthUI = null, heartbeatTimer = null, heartbeatFailCount = 0;
const KEYT_DEVICE_ID = (() => {
try {
let id = localStorage.getItem(STORAGE_KEYS.DEVICE);
if (id) return id;
let hex = '';
for (let i = 0; i < 12; i++) hex += Math.floor(Math.random() * 16).toString(16).toUpperCase();
id = hex + 'MAC';
localStorage.setItem(STORAGE_KEYS.DEVICE, id);
return id;
} catch (e) { return 'FALLBACK' + Date.now().toString(16).slice(-6).toUpperCase() + 'MAC'; }
})();
/* ========== 授权 ========== */
function apiPost(path, data) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'POST', url: CFG.AUTH_SERVER + path,
headers: { 'Content-Type': 'application/json' },
data: JSON.stringify(data || {}), timeout: 10000,
onload: r => { try { resolve(JSON.parse(r.responseText || '{}')); } catch (e) { reject(new Error('bad json')); } },
onerror: e => reject(e), ontimeout: () => reject(new Error('timeout'))
});
});
}
async function requestVerify(card, mac) {
try { const res = await apiPost('/api/verify', { card, mac }); return res && typeof res === 'object' ? res : null; }
catch (e) { return null; }
}
async function verifyAuthCode(rawCode) {
const code = (rawCode || '').trim();
if (!code) return { ok: false, msg: '请输入卡密' };
try {
const res = await requestVerify(code, KEYT_DEVICE_ID);
if (!res) return { ok: false, msg: '❌ 网络请求失败,请检查网络后重试' };
if (res.ok) {
authorized = true; authCode = code;
localStorage.setItem(STORAGE_KEYS.AUTH, code);
const days = parseInt(res.days, 10);
if (!isNaN(days) && days > 0) localStorage.setItem(STORAGE_KEYS.EXPIRE, String(Date.now() + days * 86400000));
else localStorage.removeItem(STORAGE_KEYS.EXPIRE);
startHeartbeat();
if (typeof __syncAuthUI === 'function') { try { __syncAuthUI(); } catch (e) {} }
const tail = (!isNaN(days) && days > 0) ? `(剩余 ${days} 天)` : '(终身有效)';
return { ok: true, msg: '✅ ' + (res.msg || '验证通过') + tail };
}
return { ok: false, msg: '❌ ' + (res.msg || (res.code === 'device_mismatch'
? '设备不匹配(请在后台解绑该卡密或改用新卡密)' : '验证失败')) };
} catch (e) { return { ok: false, msg: '❌ 网络请求失败,请检查网络后重试' }; }
}
function startHeartbeat() {
stopHeartbeat(); heartbeatFailCount = 0;
heartbeatTimer = setInterval(async () => {
if (!authorized || !authCode) return;
const res = await requestVerify(authCode, KEYT_DEVICE_ID);
if (res && res.ok) { heartbeatFailCount = 0; return; }
heartbeatFailCount++;
if (heartbeatFailCount >= CFG.HEARTBEAT_FAIL_LIMIT) {
authorized = false;
if (typeof stopClicking === 'function') stopClicking();
if (typeof __syncAuthUI === 'function') { try { __syncAuthUI(); } catch (e) {} }
if (typeof setStatus === 'function') setStatus('⚠️ 网络异常,卡密已保留,请重新验证', '');
if (typeof addLog === 'function') addLog('⚠️ 卡密心跳失败(已保留卡密)', 'err');
}
}, CFG.HEARTBEAT_MS);
}
function stopHeartbeat() { if (heartbeatTimer) { clearInterval(heartbeatTimer); heartbeatTimer = null; } heartbeatFailCount = 0; }
async function loadAuthFromStorage() {
let saved = '';
try { saved = (localStorage.getItem(STORAGE_KEYS.AUTH) || '').trim(); } catch (e) { return; }
if (!saved) return;
const ex = parseInt(localStorage.getItem(STORAGE_KEYS.EXPIRE) || '0', 10);
if (ex && ex < Date.now()) { try { localStorage.removeItem(STORAGE_KEYS.AUTH); localStorage.removeItem(STORAGE_KEYS.EXPIRE); } catch (e) {} return; }
const res = await requestVerify(saved, KEYT_DEVICE_ID);
if (!res || !res.ok) { authCode = saved; return; }
authCode = saved; authorized = true; startHeartbeat();
}
/* ========== 配置持久化 ========== */
function loadConfig() {
try {
const c = JSON.parse(localStorage.getItem(STORAGE_KEYS.CFG) || '{}');
if (typeof c.autoPlayVideo === 'boolean') autoPlayVideo = c.autoPlayVideo;
if (CFG.SPEED_OPTIONS.includes(c.videoSpeed)) videoSpeed = c.videoSpeed;
if (typeof c.maxClicks === 'number' && c.maxClicks >= 0) maxClicks = c.maxClicks;
} catch (e) {}
}
function saveConfig() {
try { localStorage.setItem(STORAGE_KEYS.CFG, JSON.stringify({ autoPlayVideo, videoSpeed, maxClicks })); } catch (e) {}
}
/* ========== Shadow DOM + 性能 ========== */
const shadowRootMap = new Map();
const nativeAttachShadow = Element.prototype.attachShadow;
Element.prototype.attachShadow = function (init) {
const root = nativeAttachShadow.call(this, init);
try { shadowRootMap.set(this, root); } catch (e) {}
return root;
};
function getShadowRoot(el) { if (!el || el.nodeType !== 1) return null; return el.shadowRoot || shadowRootMap.get(el) || null; }
let __rootsCache = null, __rootsObserver = null, __rootsDirty = true, __rootsLastTime = 0;
function ensureRootsObserver() {
if (__rootsObserver || !document.documentElement) return;
try { __rootsObserver = new MutationObserver(() => { __rootsDirty = true; }); __rootsObserver.observe(document.documentElement, { childList: true, subtree: true }); }
catch (e) { __rootsObserver = null; }
}
function getAllRoots() {
if (!__rootsDirty && __rootsCache) return __rootsCache;
const now = Date.now();
if (__rootsCache && (now - __rootsLastTime) < CFG.ROOTS_THROTTLE_MS) return __rootsCache;
ensureRootsObserver();
const roots = [document];
const seen = new Set(roots);
for (let i = 0; i < roots.length; i++) {
let all;
try { all = roots[i].querySelectorAll('*'); } catch (e) { continue; }
for (const el of all) { const sr = getShadowRoot(el); if (sr && !seen.has(sr)) { seen.add(sr); roots.push(sr); } }
}
__rootsCache = roots; __rootsDirty = false; __rootsLastTime = now; return roots;
}
function reportError(tag, err, showInLog) {
const msg = (err && err.message) || String(err) || '未知错误';
console.warn(`[QCC] ${tag}:`, err);
if (showInLog) addLog(`⚠️ ${tag}:${msg}`, 'err');
}
function realClick(el) {
if (!el) return false;
try {
const r0 = el.getBoundingClientRect();
if (r0.top < 0 || r0.bottom > window.innerHeight || r0.left < 0 || r0.right > window.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;
}
function pickClickTarget(el) {
let cur = el;
for (let i = 0; i < 4 && cur; i++) { try { if (getComputedStyle(cur).cursor === 'pointer') return cur; } catch (e) {} cur = cur.parentElement; }
return el;
}
function isBoldText(el) {
if (!el || el.nodeType !== 1) return false;
const tag = (el.tagName || '').toUpperCase();
if (tag === 'STRONG' || tag === 'B' || /^H[1-6]$/.test(tag)) return true;
try {
const fw = getComputedStyle(el).fontWeight;
const n = fw === 'bold' ? 700 : (fw === 'bolder' ? 800 : parseInt(fw, 10));
if (!isNaN(n) && n >= 600) return true;
} catch (e) {}
return false;
}
function getRandomDelayMs() {
const sec = Math.random() * (CFG.RANDOM_MAX_SEC - CFG.RANDOM_MIN_SEC) + CFG.RANDOM_MIN_SEC;
return Math.round(sec * 1000);
}
/* ========== Worker 后台延迟 ========== */
const workerCode = `let timer=null;self.onmessage=function(e){if(e.data&&e.data.cmd==='start'){const ms=e.data.ms||1000;if(timer)clearInterval(timer);timer=setInterval(function(){self.postMessage({tick:Date.now()});},ms);}else if(e.data&&e.data.cmd==='stop'){if(timer){clearInterval(timer);timer=null;}}};`;
let bgWorker = null;
function ensureWorker() {
if (bgWorker) return bgWorker;
try { bgWorker = new Worker(URL.createObjectURL(new Blob([workerCode], { type: 'application/javascript' }))); }
catch (e) { bgWorker = null; }
return bgWorker;
}
let __delayResolve = null;
function interruptDelay() {
if (clickTimer) { clearTimeout(clickTimer); clickTimer = null; }
if (__delayResolve) { const r = __delayResolve; __delayResolve = null; try { r(); } catch (e) {} }
}
function waitDelay(ms) {
return new Promise(resolve => {
__delayResolve = () => { __delayResolve = null; resolve(); };
const w = ensureWorker();
if (w) {
const handler = (e) => {
if (e.data && e.data.tick) {
w.removeEventListener('message', handler);
try { w.postMessage({ cmd: 'stop' }); } catch (_) {}
if (__delayResolve) { const r = __delayResolve; __delayResolve = null; r(); }
}
};
w.addEventListener('message', handler, { once: true });
try { w.postMessage({ cmd: 'start', ms }); }
catch (err) {
try { w.removeEventListener('message', handler); } catch (e2) {}
clickTimer = setTimeout(() => { if (__delayResolve) { const r = __delayResolve; __delayResolve = null; r(); } }, ms);
}
} else {
clickTimer = setTimeout(() => { if (__delayResolve) { const r = __delayResolve; __delayResolve = null; r(); } }, ms);
}
});
}
/* ========== 保活 ========== */
let keepAliveAudio = null, wakeLock = null, visibilityPatched = false;
function patchVisibility() {
if (visibilityPatched) return;
visibilityPatched = true;
try {
Object.defineProperty(document, 'hidden', { configurable: true, get: function() { return false; } });
Object.defineProperty(document, 'visibilityState', { configurable: true, get: function() { return 'visible'; } });
} catch (e) {}
}
function patchPauseMethod() {
if (window.__QCC_PAUSE_PATCHED__) return;
window.__QCC_PAUSE_PATCHED__ = true;
try {
const origPause = HTMLMediaElement.prototype.pause;
window.__QCC_ORIG_PAUSE__ = origPause;
HTMLMediaElement.prototype.pause = function() {
if (!isRunning || this.ended) return origPause.call(this);
if (document.hidden) return;
return origPause.call(this);
};
} catch (e) {}
}
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 requestWakeLock() {
try {
if ('wakeLock' in navigator && !wakeLock) {
wakeLock = await navigator.wakeLock.request('screen');
wakeLock.addEventListener('release', () => { wakeLock = null; if (isRunning) setTimeout(requestWakeLock, 1000); });
}
} catch (e) {}
}
function releaseWakeLock() { try { if (wakeLock) { wakeLock.release(); wakeLock = null; } } catch (e) {} }
function startKeepAlive() {
if (keepAliveAudio) return;
try {
const ctx = new (window.AudioContext || window.webkitAudioContext)();
if (ctx.state === 'suspended') ctx.resume().catch(() => {});
const osc = ctx.createOscillator(), gain = ctx.createGain();
osc.type = 'sine'; osc.frequency.value = 440; gain.gain.value = 0.00005;
osc.connect(gain).connect(ctx.destination); osc.start();
const resumeTimer = setInterval(() => { try { if (ctx.state === 'suspended') ctx.resume().catch(() => {}); } catch (e) {} }, 30000);
keepAliveAudio = { ctx, osc, gain, resumeTimer };
} catch (e) {}
}
function stopKeepAlive() {
try { if (keepAliveAudio) { if (keepAliveAudio.resumeTimer) clearInterval(keepAliveAudio.resumeTimer); keepAliveAudio.osc.stop(); keepAliveAudio.ctx.close(); } } catch (e) {}
keepAliveAudio = null;
}
function enableAllKeepAlive() { patchVisibility(); patchPauseMethod(); startMediaSession(); startKeepAlive(); requestWakeLock(); }
function disableAllKeepAlive() { stopKeepAlive(); releaseWakeLock(); }
/* ========== 全局状态 ========== */
let panel, statusEl, statusTextEl, listBox, listCountEl;
let __leftNavCache = null;
let selectedEls = [];
let isRunning = false, isPaused = false;
let clickTimer = null, clickIndex = 0, clickedTotal = 0, maxClicks = 0;
let expanding = false, picking = false, processingVideo = false;
const clickCountMap = new WeakMap();
/* ========== 自动缩放 ========== */
function calcAutoScale() {
const w = window.innerWidth;
const h = window.innerHeight;
let scale;
if (w >= 3600) scale = 1.40;
else if (w >= 2800) scale = 1.25;
else if (w >= 2200) scale = 1.10;
else if (w >= 1920) scale = 1.00;
else if (w >= 1600) scale = 0.92;
else if (w >= 1400) scale = 0.85;
else if (w >= 1200) scale = 0.78;
else scale = 0.72;
if (h < 720 && scale > 0.9) scale = 0.9;
if (h < 600 && scale > 0.8) scale = 0.8;
return scale;
}
function applyAutoScale() {
if (!panel) return;
const scale = calcAutoScale();
if ('zoom' in panel.style) {
panel.style.zoom = scale;
} else {
panel.style.transformOrigin = 'top right';
panel.style.transform = `scale(${scale})`;
}
}
/* ========== 统计与日志 ========== */
const runStats = { runStartTime: 0, lastRuntime: 0, videoCount: 0, videoSeconds: 0 };
let logEntries = [];
let logListEl = null, statRuntimeEl = null, statVideoEl = null, statVideosEl = null, statClicksEl = null;
let logUpdateTimer = null;
const _recordedVideos = new WeakSet();
function fmtHMS(sec) {
sec = Math.max(0, Math.floor(sec || 0));
return `${String(Math.floor(sec / 3600)).padStart(2, '0')}:${String(Math.floor((sec % 3600) / 60)).padStart(2, '0')}:${String(sec % 60).padStart(2, '0')}`;
}
function fmtMinSec(sec) {
sec = Math.max(0, sec || 0);
if (sec < 60) return `${sec.toFixed(0)}秒`;
const m = sec / 60;
if (m < 60) return `${m.toFixed(1)}分钟`;
return `${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 > CFG.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.appendChild(t); row.appendChild(x);
logListEl.insertBefore(row, logListEl.firstChild);
}
function renderLog() {
if (!logListEl) return;
if (logEntries.length === 0) { logListEl.innerHTML = '
暂无日志
'; return; }
const frag = document.createDocumentFragment();
for (let i = logEntries.length - 1; i >= 0; i--) {
const e = logEntries[i];
const row = document.createElement('div');
row.className = 'qcc-log-item' + (e.type ? ' ' + e.type : '');
const t = document.createElement('span'); t.className = 'qcc-log-time'; t.textContent = e.time;
const x = document.createElement('span'); x.className = 'qcc-log-text'; x.textContent = e.text;
row.appendChild(t); row.appendChild(x);
frag.appendChild(row);
}
logListEl.innerHTML = '';
logListEl.appendChild(frag);
}
function updateStatsUI() {
if (!statRuntimeEl) return;
const runtime = isRunning ? (Date.now() - runStats.runStartTime) / 1000 : runStats.lastRuntime;
statRuntimeEl.textContent = fmtHMS(runtime);
statVideoEl.textContent = fmtMinSec(runStats.videoSeconds);
statVideosEl.textContent = runStats.videoCount + '个';
statClicksEl.textContent = clickedTotal + '次';
}
function resetStats() {
logEntries = [];
runStats.videoCount = 0; runStats.videoSeconds = 0; runStats.lastRuntime = 0;
if (isRunning) runStats.runStartTime = Date.now();
clickedTotal = 0;
renderLog(); updateStatsUI();
}
function recordVideoWatched(video, watchedSeconds) {
if (!video || _recordedVideos.has(video)) return;
_recordedVideos.add(video);
let watched = watchedSeconds;
if (typeof watched !== 'number' || !isFinite(watched) || watched < 1) {
try {
const ct = video.currentTime || 0;
const dur = video.duration;
watched = (isFinite(dur) && dur > 0) ? Math.min(ct, dur) : ct;
} catch (e) { watched = 0; }
}
if (!isFinite(watched) || watched < 1) return;
runStats.videoSeconds += watched;
runStats.videoCount += 1;
addLog(`🎬 视频完成 +${fmtMinSec(watched)}(累计 ${fmtMinSec(runStats.videoSeconds)} / ${runStats.videoCount}个)`, 'ok');
updateStatsUI();
}
/* VIP 授权时间显示 */
function formatAuthTime() {
if (!authorized) {
return authCode ? { text: '待验证', cls: 'warn' } : { text: '未授权', cls: '' };
}
const ex = parseInt(localStorage.getItem(STORAGE_KEYS.EXPIRE) || '0', 10);
if (!ex) return { text: 'VIP · 终身有效', cls: 'ok' };
const left = ex - Date.now();
if (left <= 0) return { text: 'VIP · 已过期', cls: 'err' };
const days = Math.ceil(left / 86400000);
if (days > 30) {
const d = new Date(ex);
const str = `${d.getFullYear()}/${String(d.getMonth() + 1).padStart(2, '0')}/${String(d.getDate()).padStart(2, '0')}`;
return { text: `VIP · 至 ${str}`, cls: 'ok' };
}
if (days > 3) return { text: `VIP · 剩余 ${days} 天`, cls: 'ok' };
return { text: `VIP · 仅剩 ${days} 天`, cls: 'warn' };
}
function updateAuthTimeUI() {
const at = document.getElementById('qcc-auth-time');
if (!at) return;
const info = formatAuthTime();
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 txt = (el.textContent || '').trim().replace(/\s+/g, ' ');
if (!txt) return `[${(el.tagName || 'el').toLowerCase()}]`;
return txt.length > 30 ? txt.slice(0, 30) + '…' : txt;
}
function renderList() {
if (!listBox || !listCountEl) return;
listCountEl.textContent = selectedEls.length;
if (selectedEls.length === 0) { listBox.innerHTML = '暂无元素'; return; }
listBox.innerHTML = '';
selectedEls.forEach((el, i) => {
const item = document.createElement('div'); item.className = 'qcc-list-item';
const txt = document.createElement('span'); txt.className = 'qcc-list-item-text';
const cnt = clickCountMap.get(el) || 0;
txt.textContent = `${i + 1}. ${getElText(el)}${cnt > 0 ? ' ×' + cnt : ''}`;
const del = document.createElement('button'); del.className = 'qcc-list-item-del'; del.textContent = '×';
del.addEventListener('click', (e) => { e.stopPropagation(); removeSelected(el); });
item.appendChild(txt); item.appendChild(del); listBox.appendChild(item);
});
}
function removeSelected(el) { try { el.classList.remove('qcc-pick-selected'); } catch (e) {} const i = selectedEls.indexOf(el); if (i > -1) selectedEls.splice(i, 1); renderList(); }
function clearSelected() { selectedEls.forEach(el => { try { el.classList.remove('qcc-pick-selected'); } catch (e) {} }); selectedEls = []; renderList(); }
function updateRunButtons() {
const b1 = document.getElementById('qcc-start');
const b2 = document.getElementById('qcc-stop');
const b3 = document.getElementById('qcc-pause');
if (b1) b1.disabled = isRunning;
if (b2) b2.disabled = !isRunning;
if (b3) {
b3.disabled = !isRunning;
b3.innerHTML = isPaused
? '▶ 继续'
: '⏸ 暂停';
}
}
/* ========== 手动拾取 ========== */
let manualPicking = false, manualHoverEl = null, manualOverHandler = null, manualClickHandler = null;
function pickManualTarget(el) {
if (!el || el.nodeType !== 1) return null;
if (panel && panel.contains(el)) return null;
let cur = el;
for (let i = 0; i < 5 && cur; i++) {
const tag = (cur.tagName || '').toUpperCase();
if (tag === 'A') return cur;
if (cur.getAttribute && cur.getAttribute('href')) return cur;
try { if (getComputedStyle(cur).cursor === 'pointer') return cur; } catch (e) {}
cur = cur.parentElement;
}
return el;
}
function updatePickButton() {
const btn = document.getElementById('qcc-auto-pick');
if (!btn) return;
if (authorized) btn.innerHTML = '🔍 自动选择';
else if (manualPicking) btn.innerHTML = '✋ 退出手动';
else btn.innerHTML = '👆 手动选择';
}
function enterManualPick() {
if (manualPicking) return;
manualPicking = true;
manualOverHandler = (e) => {
const el = pickManualTarget(e.target);
if (!el || manualHoverEl === el) return;
if (manualHoverEl) { try { manualHoverEl.classList.remove('qcc-manual-hover'); } catch (err) {} }
manualHoverEl = el;
try { el.classList.add('qcc-manual-hover'); } catch (err) {}
};
manualClickHandler = (e) => {
if (panel && panel.contains(e.target)) return;
e.preventDefault(); e.stopPropagation();
const el = pickManualTarget(e.target);
if (!el || selectedEls.includes(el)) return;
selectedEls.push(el);
try { el.classList.add('qcc-pick-selected'); } catch (err) {}
renderList();
setStatus(`👆 手动选择中… 已添加 ${selectedEls.length} 个元素`, 'running');
};
document.addEventListener('mouseover', manualOverHandler, true);
document.addEventListener('click', manualClickHandler, true);
try { document.body.style.cursor = 'crosshair'; } catch (e) {}
updatePickButton();
setStatus('👆 手动选择:点击页面元素添加(再次点击按钮或 ESC 退出)', 'running');
}
function exitManualPick() {
if (!manualPicking) return;
manualPicking = false;
if (manualOverHandler) { try { document.removeEventListener('mouseover', manualOverHandler, true); } catch (e) {} manualOverHandler = null; }
if (manualClickHandler) { try { document.removeEventListener('click', manualClickHandler, true); } catch (e) {} manualClickHandler = null; }
if (manualHoverEl) { try { manualHoverEl.classList.remove('qcc-manual-hover'); } catch (e) {} manualHoverEl = null; }
try { document.body.style.cursor = ''; } catch (e) {}
updatePickButton();
setStatus(`已退出手动选择,共 ${selectedEls.length} 个元素`, '');
}
/* ========== 展开 ========== */
const sleep = ms => new Promise(r => setTimeout(r, ms));
async function expandAllCollapsed() {
if (expanding) return;
expanding = true;
__leftNavCache = null; __rootsDirty = true;
const startTime = Date.now();
const deadline = startTime + CFG.EXPAND_HARD_LIMIT_MS;
let totalClicked = 0;
const clickedOnce = new WeakSet();
setStatus('🔍 正在展开目录…', 'running');
addLog('📂 开始展开目录…', '');
try {
for (let loop = 0; loop < CFG.EXPAND_MAX_LOOPS; loop++) {
if (Date.now() > deadline) break;
const arrows = [];
const seen = new Set();
for (const root of getAllRoots()) {
let items;
try { items = root.querySelectorAll('[class*="sub-menu-title"]'); } catch (e) { continue; }
for (const el of items) {
if (seen.has(el) || clickedOnce.has(el)) continue;
seen.add(el);
if (panel && panel.contains(el)) continue;
const svg = el.querySelector('svg.svg-icon');
if (!svg) continue;
const style = svg.getAttribute('style') || '';
if (style.includes('rotate(180deg)')) continue;
arrows.push({ svg, parent: el });
}
}
if (arrows.length === 0) break;
setStatus(`展开中… 剩 ${arrows.length} 个,已点 ${totalClicked}`, 'running');
for (const { svg, parent } of arrows) {
if (Date.now() > deadline) break;
if (!svg.isConnected) continue;
try { realClick(svg); totalClicked++; clickedOnce.add(parent); } catch (e) {}
await sleep(CFG.EXPAND_CLICK_GAP_MS);
}
await sleep(CFG.EXPAND_LOOP_GAP_MS);
__leftNavCache = null;
}
const elapsed = ((Date.now() - startTime) / 1000).toFixed(2);
const hitLimit = Date.now() - startTime >= CFG.EXPAND_HARD_LIMIT_MS - 50;
setStatus(hitLimit ? `展开完成 ${totalClicked} 个(${elapsed}秒,达上限)` : `展开完成,共点击 ${totalClicked} 个(${elapsed}秒)`, '');
addLog(`📂 展开完成:点击 ${totalClicked} 个(${elapsed}s)`, 'ok');
} finally {
expanding = false;
__leftNavCache = null; __rootsDirty = true;
}
}
/* ========== 拾取 ========== */
function findLeftNav() {
if (__leftNavCache && __leftNavCache.isConnected) return __leftNavCache;
for (const root of getAllRoots()) {
let all;
try { all = root.querySelectorAll('*'); } catch (e) { continue; }
for (const el of all) {
if (panel && panel.contains(el)) continue;
let st;
try { st = getComputedStyle(el); } catch (e) { continue; }
if (st.overflowY !== 'auto' && st.overflowY !== 'scroll') continue;
let r;
try { r = el.getBoundingClientRect(); } catch (e) { continue; }
if (r.width < 100 || r.width > 400 || r.left > 100) continue;
if (el.scrollHeight < el.clientHeight) continue;
__leftNavCache = el; return el;
}
}
return null;
}
function isInLeftNav(el) {
const nav = findLeftNav();
if (!nav) { try { const r = el.getBoundingClientRect(); return r.left >= 0 && r.left <= 280; } catch (e) { return false; } }
return nav.contains(el);
}
function resolveClickTarget(el) {
let cur = el;
for (let i = 0; i < 4 && cur; i++) {
const tag = (cur.tagName || '').toUpperCase();
if (tag === 'A') return cur;
if (cur.getAttribute && cur.getAttribute('href')) return cur;
try { if (getComputedStyle(cur).cursor === 'pointer') return cur; } catch (e) {}
cur = cur.parentElement;
}
return el;
}
function collectOnce(intoSet) {
const roots = getAllRoots();
let hits = 0;
for (const root of roots) {
let list;
try { list = root.querySelectorAll('.text-too-long'); } catch (e) { list = []; }
for (const el of list) {
if (panel && panel.contains(el)) continue;
if (isBoldText(el)) continue;
if (!isInLeftNav(el)) continue;
let r;
try { r = el.getBoundingClientRect(); } catch (e) { continue; }
if (r.width < 20 || r.height < 8) continue;
let st;
try { st = getComputedStyle(el); } catch (e) { continue; }
if (st.display === 'none' || st.visibility === 'hidden') continue;
if (!(el.textContent || '').trim()) continue;
intoSet.add(resolveClickTarget(el)); hits++;
}
}
if (hits === 0) {
for (const root of roots) {
let all;
try { all = root.querySelectorAll('*'); } catch (e) { continue; }
for (const el of all) {
if (panel && panel.contains(el)) continue;
if (isBoldText(el)) continue;
if (!isInLeftNav(el)) continue;
let r;
try { r = el.getBoundingClientRect(); } catch (e) { continue; }
if (r.width < 30 || r.height < 12 || r.height > 60) continue;
let st;
try { st = getComputedStyle(el); } catch (e) { continue; }
if (st.cursor !== 'pointer' || st.display === 'none') continue;
const txt = (el.textContent || '').trim();
if (!txt || txt.length > 80) continue;
intoSet.add(el);
}
}
}
}
async function autoPickLeftNav() {
if (picking) return;
picking = true;
clearSelected();
__leftNavCache = null;
setStatus('🔍 正在拾取…', 'running');
try {
await sleep(200);
__leftNavCache = null;
const collected = new Set();
collectOnce(collected);
for (const el of collected) {
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;
__leftNavCache = null;
}
}
/* ========== 视频 ========== */
let autoPlayVideo = false, videoSpeed = 2;
function getAllDocuments() {
const docs = [document];
for (let i = 0; i < docs.length; i++) {
let iframes;
try { iframes = docs[i].querySelectorAll('iframe'); } catch (e) { continue; }
for (const f of iframes) { try { const d = f.contentDocument; if (d && !docs.includes(d)) docs.push(d); } catch (e) {} }
}
return docs;
}
function getAllVideoElements() {
const seen = new Set(), result = [];
for (const root of getAllRoots()) {
let vs;
try { vs = root.querySelectorAll('video'); } catch (e) { continue; }
for (const v of vs) { if (v.isConnected && !seen.has(v)) { seen.add(v); result.push(v); } }
}
for (const doc of getAllDocuments()) {
let vs;
try { vs = doc.querySelectorAll('video'); } catch (e) { continue; }
for (const v of vs) { if (v.isConnected && !seen.has(v)) { seen.add(v); result.push(v); } }
}
return result;
}
function videoKey(v) { return (v.currentSrc || v.src || '') + '::' + (v.getAttribute('data-src') || ''); }
function snapshotVideoKeys() {
const set = new Set();
for (const v of getAllVideoElements()) set.add(videoKey(v));
return set;
}
async function waitForNewVideo(oldKeys, maxWaitMs) {
const start = Date.now();
while (Date.now() - start < maxWaitMs) {
for (const v of getAllVideoElements()) {
const k = videoKey(v);
if (k && !oldKeys.has(k)) return v;
}
await sleep(CFG.VIDEO_POLL_MS);
}
return null;
}
async function waitMetadata(v, maxWaitMs = 8000) {
if (v.readyState >= 1) return true;
return await new Promise(resolve => {
const timer = setTimeout(() => { v.removeEventListener('loadedmetadata', onL); resolve(false); }, maxWaitMs);
const onL = () => { clearTimeout(timer); resolve(true); };
v.addEventListener('loadedmetadata', onL, { once: true });
});
}
async function playVideoToEnd(video) {
await waitMetadata(video, 8000);
const forceRate = () => {
try {
if (video.playbackRate !== videoSpeed) video.playbackRate = videoSpeed;
if (video.defaultPlaybackRate !== videoSpeed) video.defaultPlaybackRate = videoSpeed;
} catch (e) {}
};
const forceMute = () => { try { if (!video.muted) video.muted = true; if (video.volume !== 0) video.volume = 0; } catch (e) {} };
const onPause = () => {
if (!isRunning) return;
setTimeout(() => { try { if (video.paused && video.isConnected && isRunning) video.play().catch(() => {}); } catch (e) {} }, 200);
};
const onRC = () => { if (video.playbackRate !== videoSpeed) forceRate(); };
const onVC = () => { forceMute(); };
let watchedSeconds = 0, lastCT = 0;
const onTimeUpdate = () => {
const ct = video.currentTime || 0;
const delta = ct - lastCT;
if (delta > 0 && delta < 2) watchedSeconds += delta;
lastCT = ct;
};
video.addEventListener('ratechange', onRC);
video.addEventListener('pause', onPause);
video.addEventListener('volumechange', onVC);
video.addEventListener('timeupdate', onTimeUpdate);
const rateTimer = setInterval(forceRate, 500);
const keepAliveTimer = setInterval(() => {
if (!isRunning) return;
try {
if (video.paused && !video.ended && video.isConnected) video.play().catch(() => {});
forceMute(); forceRate();
} catch (e) {}
}, 2000);
const cleanup = () => {
video.removeEventListener('ratechange', onRC);
video.removeEventListener('pause', onPause);
video.removeEventListener('volumechange', onVC);
video.removeEventListener('timeupdate', onTimeUpdate);
clearInterval(rateTimer); clearInterval(keepAliveTimer);
};
try {
video.muted = true; video.volume = 0; forceRate();
try { video.currentTime = 0; } catch (e) {}
await video.play(); forceRate();
try { video.currentTime = 0; } catch (e) {}
} catch (e) {
reportError('视频 play() 失败', e, true);
cleanup(); return false;
}
await new Promise(resolve => {
let last = -1, stuck = 0;
const st = Date.now();
const check = () => {
if (!video.isConnected || video.ended) { resolve(); return; }
const ct = video.currentTime || 0, dur = video.duration || 0;
if (dur > 0 && isFinite(dur) && ct >= dur - 0.3) { resolve(); return; }
if (Date.now() - st > CFG.VIDEO_PLAY_GRACE_MS) {
if (ct > 0 && Math.abs(ct - last) < 0.05) { stuck++; if (stuck > CFG.VIDEO_STUCK_LIMIT) { resolve(); return; } }
else stuck = 0;
}
last = ct; forceRate(); forceMute(); setTimeout(check, 500);
};
check();
});
cleanup();
try { recordVideoWatched(video, watchedSeconds); } catch (e) {}
return true;
}
/* ========== 主循环 ========== */
function highlightClick(el) { try { el.classList.add('qcc-clicking'); } catch (e) {} setTimeout(() => { try { el.classList.remove('qcc-clicking'); } catch (e) {} }, 200); }
async function doClick() {
if (!isRunning || processingVideo) return;
if (selectedEls.length === 0) { stopClicking(); setStatus('没有可点击的元素', ''); return; }
selectedEls = selectedEls.filter(el => el.isConnected);
if (selectedEls.length === 0) { 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 = snapshotVideoKeys();
const label = getElText(el);
try { realClick(pickClickTarget(el) || el); highlightClick(el); } catch (err) {}
clickedTotal++; clickIndex++;
clickCountMap.set(el, (clickCountMap.get(el) || 0) + 1);
renderList();
setStatus(`执行中… [${label}] 已点击 ${clickedTotal} 次`, 'running');
addLog(`👆 第 ${clickedTotal} 次:${label}`, '');
updateStatsUI();
if (!autoPlayVideo) return;
const video = await waitForNewVideo(oldKeys, CFG.VIDEO_WAIT_MS);
if (!video) { addLog('⏳ 未检测到新视频(跳过)', ''); return; }
processingVideo = true;
updateRunButtons();
setStatus(`🎬 检测到新视频,正在 ${videoSpeed} 倍速静音播放…`, 'running');
addLog(`🎬 检测到视频,${videoSpeed}x 静音播放中…`, '');
try { await playVideoToEnd(video); if (isRunning) setStatus('✅ 视频播放完成,准备下一个', ''); }
catch (e) { reportError('播放异常', 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;
const delay = getRandomDelayMs();
await waitDelay(delay);
}
} finally {
loopRunning = false;
}
}
function startClicking() {
if (isRunning) return;
if (selectedEls.length === 0) { setStatus('请先选择左侧目录元素', ''); return; }
if (manualPicking) exitManualPick();
interruptDelay();
try { if (bgWorker) bgWorker.postMessage({ cmd: 'stop' }); } catch (e) {}
isRunning = true; isPaused = false; processingVideo = false;
clickIndex = 0; clickedTotal = 0;
runStats.runStartTime = Date.now();
runStats.videoCount = 0; runStats.videoSeconds = 0; runStats.lastRuntime = 0;
updateStatsUI();
enableAllKeepAlive();
updateRunButtons();
setStatus('开始执行…(无限循环,随机间隔 2~12 秒)', 'running');
addLog(`▶ 开始运行(${selectedEls.length} 个元素,间隔 2~12 秒,${autoPlayVideo ? videoSpeed + 'x 自动播放' : '⚠ 未开启自动播放'}${maxClicks > 0 ? ',上限 ' + maxClicks + ' 次' : ''})`, 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) return;
isRunning = false; isPaused = false;
interruptDelay();
disableAllKeepAlive();
try { if (bgWorker) bgWorker.postMessage({ cmd: 'stop' }); } catch (e) {}
runStats.lastRuntime = runStats.runStartTime ? (Date.now() - runStats.runStartTime) / 1000 : 0;
updateRunButtons();
if (statusEl && statusEl.classList.contains('running')) setStatus(`已停止,共点击 ${clickedTotal} 次`, '');
addLog(`■ 已停止 · 运行 ${fmtHMS(runStats.lastRuntime)} · 点击 ${clickedTotal} 次 · 视频 ${runStats.videoCount} 个(${fmtMinSec(runStats.videoSeconds)})`, 'err');
updateStatsUI();
}
async function playCurrentPageVideo() {
if (processingVideo) { setStatus('已有视频正在处理中', ''); return; }
if (!autoPlayVideo) { setStatus('🔒 请先开启「自动播放视频」开关', ''); return; }
const videos = getAllVideoElements();
if (videos.length === 0) { setStatus('当前页面没检测到视频', ''); addLog('🎬 当前页面无视频', 'err'); return; }
const v = videos.find(x => !x.ended) || videos[0];
processingVideo = true; updateRunButtons();
setStatus(`🎬 手动播放视频,${videoSpeed} 倍速静音…`, 'running');
addLog(`🎬 手动播放当前页面视频`, '');
try { await playVideoToEnd(v); setStatus('✅ 手动播放完成', ''); }
catch (e) { reportError('手动播放失败', e, true); }
finally { processingVideo = false; updateRunButtons(); }
}
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 openDonateModal() {
if (document.getElementById('qcc-donate-modal')) return;
const modal = document.createElement('div'); modal.id = 'qcc-donate-modal';
modal.innerHTML = `
❤️
感谢您的支持
如果这个脚本帮到了您,
欢迎随意打赏一杯咖啡~
QQ:3365137745
群号:1124065231
授权后可解锁「自动播放视频」和「一键展开」
🎁 半天试用卡
${CFG.TRIAL_CODE}
🔐 卡密验证
输入卡密后点击「确认」解锁功能
`;
document.body.appendChild(modal);
const inputEl = document.getElementById('qcc-donate-auth-input');
const btnEl = document.getElementById('qcc-donate-auth-btn');
const msgEl = document.getElementById('qcc-donate-auth-msg');
const authWrap = 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(CFG.TRIAL_CODE);
ok = true;
} catch (e) {
const ta = document.createElement('textarea'); ta.value = CFG.TRIAL_CODE;
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 (inputEl && !inputEl.disabled) inputEl.value = CFG.TRIAL_CODE;
setTimeout(() => { b.textContent = '复制'; b.classList.remove('copied'); }, 1500);
}
});
let esc = null;
function closeModal() { try { modal.remove(); } catch (e) {} if (esc) document.removeEventListener('keydown', esc); }
function syncModalAuth() {
if (authorized) {
authWrap.classList.add('is-ok'); inputEl.value = authCode; inputEl.disabled = true;
btnEl.disabled = true; btnEl.textContent = '已授权';
msgEl.textContent = '✅ 功能已解锁,可以使用了'; msgEl.className = 'qcc-auth-msg ok';
} else {
authWrap.classList.remove('is-ok'); inputEl.disabled = false;
btnEl.disabled = false; btnEl.textContent = '确认';
msgEl.textContent = authCode ? '⚠️ 上次验证未通过,请重新验证' : '输入卡密后点击「确认」解锁功能';
msgEl.className = 'qcc-auth-msg';
if (authCode) inputEl.value = authCode;
}
}
async function tryAuth() {
btnEl.disabled = true; btnEl.textContent = '验证中…';
const res = await verifyAuthCode(inputEl.value);
btnEl.disabled = false;
if (res.ok) { syncModalAuth(); setStatus(res.msg, ''); addLog('🔐 卡密验证成功', 'ok'); setTimeout(closeModal, 1200); }
else {
msgEl.textContent = res.msg; msgEl.className = 'qcc-auth-msg err';
setStatus(res.msg.replace(/[✅❌⚠️]\s*/g, ''), '');
addLog('🔐 ' + res.msg.replace(/[✅❌⚠️]\s*/g, ''), 'err');
authWrap.classList.remove('qcc-shake'); void authWrap.offsetWidth; authWrap.classList.add('qcc-shake');
try { inputEl.focus(); inputEl.select(); } catch (e) {}
btnEl.textContent = '确认';
}
}
btnEl.addEventListener('click', tryAuth);
inputEl.addEventListener('keydown', e => { if (e.key === 'Enter') { e.preventDefault(); tryAuth(); } });
modal.addEventListener('click', e => { if (e.target === modal) closeModal(); });
document.getElementById('qcc-donate-close').addEventListener('click', closeModal);
esc = e => { if (e.key === 'Escape') closeModal(); };
document.addEventListener('keydown', esc);
syncModalAuth();
if (!authorized) setTimeout(() => { try { inputEl.focus(); } catch (e) {} }, 120);
}
/* ========== 面板 ========== */
function buildPanel() {
if (document.getElementById('quick-custom-clicker')) return;
loadConfig();
panel = document.createElement('div');
panel.id = 'quick-custom-clicker';
panel.innerHTML = `
⚙️ 参数设置
未授权
2~12s 随机
ℹ️ 说明:先 [一键展开] → 后 [自动选择] → 再 [开始]
📌 说明:赠送试用卡 点[捐赠] → 再 [复制] →点 [确认]
等待操作
运行 00:00:00
视频 0分钟 (0个)
点击 0次
`;
const style = document.createElement('style');
style.textContent = `
#quick-custom-clicker{position:fixed;top:100px;right:20px;width:280px;background:rgba(255,255,255,0.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,0.18),0 8px 24px rgba(15,23,42,0.10),0 1px 0 rgba(255,255,255,0.6) inset,0 0 0 0.5px rgba(15,23,42,0.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);-webkit-font-smoothing:antialiased;letter-spacing:-0.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:0.5px solid rgba(15,23,42,0.08);}
.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,0.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,0.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(-0.5px);}
.qcc-icon-btn:hover{background:rgba(255,255,255,0.38);}
.qcc-icon-btn#qcc-close:hover{background:rgba(255,59,48,0.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,0.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;}
.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;
letter-spacing:0;
text-transform:none;
font-variant-numeric:tabular-nums;
white-space:nowrap;
display:inline-flex;
align-items:center;
box-shadow:0 2px 6px rgba(18,183,106,0.28);
}
.qcc-auth-time.ok{color:#fff;background:linear-gradient(180deg,#34d17a 0%,#12b76a 100%);}
.qcc-auth-time.warn{color:#fff;background:linear-gradient(180deg,#ffb340 0%,#f79009 100%);box-shadow:0 2px 6px rgba(247,144,9,0.28);}
.qcc-auth-time.err{color:#fff;background:linear-gradient(180deg,#ff6b61 0%,#ff3b30 100%);box-shadow:0 2px 6px rgba(255,59,48,0.28);}
.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{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:0.5px solid rgba(15,23,42,0.06);gap:8px;}
.qcc-field-row label{font-size:11px;color:#3a3a3c;font-weight:500;flex:0 0 auto;white-space:nowrap;}
.qcc-field-compact{padding:6px 0;}
.qcc-interval-value{font-size:10px;font-weight:600;color:#4f6ef7;background:rgba(79,110,247,0.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:rgba(15,23,42,0.05);
color:#3a3a3c;
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;
}
.qcc-inline-btn:hover{background:rgba(15,23,42,0.09);}
.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,0.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{
background:linear-gradient(180deg,#43dc8a 0%,#22c876 100%) !important;
box-shadow:0 2px 8px rgba(18,183,106,0.38) !important;
}
.qcc-num-input:focus{
box-shadow:0 0 0 3px rgba(18,183,106,0.25),0 2px 8px rgba(18,183,106,0.4) !important;
}
.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{
font-size:11px;
color:#3a3a3c;
white-space:nowrap;
flex:0 0 auto;
line-height:20px;
height:20px;
display:inline-flex;
align-items:center;
}
.qcc-double-item .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;
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-primary{background:linear-gradient(180deg,#5b78ff 0%,#4f6ef7 100%);color:#fff;box-shadow:0 4px 14px rgba(79,110,247,0.30),0 1px 0 rgba(255,255,255,0.2) inset;}
.qcc-btn-primary:not(:disabled):hover{background:linear-gradient(180deg,#6b85ff 0%,#5b78ff 100%);transform:translateY(-1px);}
.qcc-btn-success{background:linear-gradient(180deg,#34d17a 0%,#12b76a 100%);color:#fff;box-shadow:0 4px 14px rgba(18,183,106,0.30);}
.qcc-btn-success:not(:disabled):hover{background:linear-gradient(180deg,#43dc8a 0%,#22c876 100%);transform:translateY(-1px);}
.qcc-btn-danger{background:rgba(255,59,48,0.08);color:#ff3b30;}
.qcc-btn-danger:not(:disabled):hover{background:rgba(255,59,48,0.15);}
.qcc-btn-ghost{background:rgba(15,23,42,0.05);color:#3a3a3c;}
.qcc-btn-ghost:not(:disabled):hover{background:rgba(15,23,42,0.09);}
.qcc-btn-donate{background:linear-gradient(180deg,#ff7aa2 0%,#ff4d7d 100%);color:#fff;box-shadow:0 4px 14px rgba(255,77,125,0.30);}
.qcc-btn-donate:not(:disabled):hover{background:linear-gradient(180deg,#ff8aae 0%,#ff5c88 100%);transform:translateY(-1px);}
.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,0.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,0.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-box{background:rgba(15,23,42,0.04);border-radius:10px;padding:8px 10px;margin-bottom:12px;}
.qcc-list-header{display:flex;justify-content:space-between;align-items:center;font-size:10px;color:#8e8e93;font-weight:600;margin-bottom:6px;}
.qcc-list-count{background:rgba(15,23,42,0.08);color:#3a3a3c;padding:2px 7px;border-radius:6px;font-size:9.5px;font-weight:600;}
.qcc-list{max-height:90px;min-height:32px;overflow-y:auto;font-size:10.5px;color:#3a3a3c;}
.qcc-list-item{display:flex;justify-content:space-between;align-items:center;gap:6px;padding:6px 8px;background:rgba(255,255,255,0.7);border-radius:8px;margin-bottom:4px;border:0.5px solid rgba(15,23,42,0.06);}
.qcc-list-item-text{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.qcc-list-item-del{border:0;background:transparent;color:#c7c7cc;cursor:pointer;font-size:12px;padding:0 2px;}
.qcc-list-item-del:hover{color:#ff3b30;}
.qcc-pick-selected{outline:2px solid #12b76a !important;outline-offset:1px !important;background:rgba(18,183,106,0.10) !important;border-radius:4px;}
.qcc-clicking{outline:3px solid #f79009 !important;outline-offset:1px !important;}
.qcc-manual-hover{outline:2px dashed #4f6ef7 !important;outline-offset:2px !important;background:rgba(79,110,247,0.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,0.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-speed-btns{display:flex;gap:4px;margin-top:5px;}
.qcc-speed-btn{
flex:1;
height:24px;
border-radius:8px;
border:0.5px solid rgba(15,23,42,0.10);
background:rgba(255,255,255,0.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,0.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,0.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,0.30);}
#quick-custom-clicker.qcc-collapsed #qcc-close{display:none;}
.qcc-log-box{background:rgba(15,23,42,0.04);border-radius:10px;padding:8px 10px;margin-bottom:12px;}
.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,0.10);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:0.5px dashed rgba(15,23,42,0.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-track{background:transparent;border-radius:5px;}
.qcc-log-list::-webkit-scrollbar-thumb{
background:rgba(120,120,120,0.55);
border-radius:5px;
border:2px solid transparent;
background-clip:content-box;
}
.qcc-log-list::-webkit-scrollbar-thumb:hover{
background:rgba(100,100,100,0.75);
border:2px solid transparent;
background-clip:content-box;
}
.qcc-log-list::-webkit-scrollbar-button{display:none;width:0;height:0;}
.qcc-log-item{display:flex;gap:6px;padding:2px 0;border-bottom:0.5px solid rgba(15,23,42,0.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,0.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,0.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,0.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,0.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,0.12) 0%,rgba(255,120,70,0.12) 100%);border:0.5px solid rgba(255,149,0,0.30);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,0.30);transition:all .18s;display:inline-flex;align-items:center;}
.qcc-trial-copy:hover{transform:translateY(-1px);}
.qcc-trial-copy:active{transform:scale(.97);}
.qcc-trial-copy.copied{background:linear-gradient(180deg,#34d17a 0%,#12b76a 100%);box-shadow:0 2px 6px rgba(18,183,106,0.30);}
.qcc-trial-code{font-size:12px;font-weight:700;color:#1d1d1f;font-family:ui-monospace,Menlo,monospace;letter-spacing:.10em;word-break:break-all;background:rgba(255,255,255,0.75);padding:6px 9px;border-radius:6px;user-select:text;text-align:center;}
.qcc-donate-auth{text-align:left;background:rgba(255,149,0,0.08);border:0.5px solid rgba(255,149,0,0.22);border-radius:10px;padding:10px;margin-bottom:12px;transition:.2s;}
.qcc-donate-auth.is-ok{background:rgba(18,183,106,0.08);border-color:rgba(18,183,106,0.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:0.5px solid rgba(15,23,42,0.12);background:rgba(255,255,255,0.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,0.5);box-shadow:0 0 0 3px rgba(79,110,247,0.12);}
.qcc-auth-input:disabled{background:rgba(15,23,42,0.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,0.3);}
.qcc-auth-btn:disabled{opacity:.6;background:rgba(18,183,106,0.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,0.06);color:#3a3a3c;font-size:11.5px;cursor:pointer;font-family:inherit;}
.qcc-donate-close:hover{background:rgba(15,23,42,0.11);}
@media (prefers-color-scheme: dark) {
#quick-custom-clicker{background:rgba(30,30,32,0.85);color:#f5f5f7;box-shadow:0 20px 60px rgba(0,0,0,0.5),0 8px 24px rgba(0,0,0,0.3),0 1px 0 rgba(255,255,255,0.06) inset,0 0 0 0.5px rgba(255,255,255,0.06);}
#quick-custom-clicker .qcc-body::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.18);}
#quick-custom-clicker .qcc-section-title{color:#8e8e93;}
#quick-custom-clicker .qcc-field-head label,
#quick-custom-clicker .qcc-field-row label,
#quick-custom-clicker .qcc-double-item label{color:#c7c7cc;}
#quick-custom-clicker .qcc-status{background:rgba(255,255,255,0.06);color:#c7c7cc;}
#quick-custom-clicker .qcc-list-box,
#quick-custom-clicker .qcc-log-box{background:rgba(255,255,255,0.05);}
#quick-custom-clicker .qcc-list-item{background:rgba(255,255,255,0.06);border-color:rgba(255,255,255,0.08);color:#f5f5f7;}
#quick-custom-clicker .qcc-list-item-text{color:#f5f5f7;}
#quick-custom-clicker .qcc-list-header{color:#8e8e93;}
#quick-custom-clicker .qcc-list-count{background:rgba(255,255,255,0.10);color:#c7c7cc;}
#quick-custom-clicker .qcc-stat-compact{color:#8e8e93;border-bottom-color:rgba(255,255,255,0.10);}
#quick-custom-clicker .qcc-stat-compact b{color:#f5f5f7;}
#quick-custom-clicker .qcc-log-text{color:#c7c7cc;}
#quick-custom-clicker .qcc-log-time{color:#8e8e93;}
#quick-custom-clicker .qcc-log-item{border-bottom-color:rgba(255,255,255,0.05);}
#quick-custom-clicker .qcc-btn-ghost{background:rgba(255,255,255,0.08);color:#c7c7cc;}
#quick-custom-clicker .qcc-btn-ghost:not(:disabled):hover{background:rgba(255,255,255,0.14);}
#quick-custom-clicker .qcc-inline-btn{background:rgba(255,255,255,0.08);color:#c7c7cc;}
#quick-custom-clicker .qcc-inline-btn:hover{background:rgba(255,255,255,0.14);}
#quick-custom-clicker .qcc-speed-btn{background:rgba(255,255,255,0.08);color:#c7c7cc;border-color:rgba(255,255,255,0.12);}
#quick-custom-clicker .qcc-speed-btn:hover{background:rgba(255,255,255,0.15);}
#quick-custom-clicker .qcc-log-list::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.35);border:2px solid transparent;background-clip:content-box;}
#quick-custom-clicker .qcc-log-list::-webkit-scrollbar-thumb:hover{background:rgba(255,255,255,0.50);border:2px solid transparent;background-clip:content-box;}
.qcc-trial-label{color:#fbbf24;}
.qcc-trial-code{background:rgba(255,255,255,0.10);color:#f5f5f7;}
}`;
document.head.appendChild(style);
document.body.appendChild(panel);
applyAutoScale();
statusEl = document.getElementById('qcc-status');
statusTextEl = document.getElementById('qcc-status-text');
listBox = document.getElementById('qcc-list');
listCountEl = document.getElementById('qcc-list-count');
logListEl = document.getElementById('qcc-log-list');
statRuntimeEl = document.getElementById('qcc-stat-runtime');
statVideoEl = document.getElementById('qcc-stat-video');
statVideosEl = document.getElementById('qcc-stat-videos');
statClicksEl = document.getElementById('qcc-stat-clicks');
if (logUpdateTimer) clearInterval(logUpdateTimer);
logUpdateTimer = setInterval(() => {
updateStatsUI();
updateAuthTimeUI();
}, 1000);
renderLog(); updateStatsUI(); updateAuthTimeUI();
document.getElementById('qcc-log-reset').addEventListener('click', () => { resetStats(); setStatus('日志与统计已重置', ''); });
document.getElementById('qcc-log-export').addEventListener('click', exportLogs);
const autoVideoCheck = document.getElementById('qcc-auto-video');
const speedBlock = document.getElementById('qcc-speed-block');
const speedValueEl = document.getElementById('qcc-speed-value');
const speedBtns = panel.querySelectorAll('.qcc-speed-btn');
const maxClicksInput = document.getElementById('qcc-max-clicks');
maxClicksInput.value = String(maxClicks || 0);
maxClicksInput.addEventListener('input', () => {
const n = parseInt(maxClicksInput.value, 10);
maxClicks = (isNaN(n) || n < 0) ? 0 : n;
saveConfig();
});
maxClicksInput.addEventListener('blur', () => {
maxClicksInput.value = String(maxClicks || 0);
});
__syncAuthUI = function () {
updatePickButton();
const vl = document.getElementById('qcc-auto-video-label');
if (vl) vl.textContent = authorized ? '自动播放' : '自动播放 🔒';
const et = document.getElementById('qcc-expandRight-text');
if (et) et.textContent = authorized ? '一键展开' : '一键展开 🔒';
updateAuthTimeUI();
if (authorized) { if (manualPicking) exitManualPick(); setStatus('✅ 已授权,功能可正常使用', ''); }
};
function syncVideoUI() {
autoVideoCheck.checked = autoPlayVideo;
speedBlock.classList.toggle('qcc-hidden', !autoPlayVideo);
speedValueEl.textContent = videoSpeed + 'x';
speedBtns.forEach(b => b.classList.toggle('active', parseInt(b.dataset.speed, 10) === videoSpeed));
updateRunButtons();
}
function flashDonateBtn() {
const btn = document.getElementById('qcc-donate');
if (!btn) return;
btn.style.transition = 'all .2s'; btn.style.transform = 'scale(1.08)';
btn.style.boxShadow = '0 0 0 4px rgba(255,77,125,0.3)';
setTimeout(() => { btn.style.transform = 'scale(1)'; btn.style.boxShadow = ''; }, 400);
}
autoVideoCheck.addEventListener('click', e => {
if (!authorized) { e.preventDefault(); autoVideoCheck.checked = false; autoPlayVideo = false; syncVideoUI();
setStatus('🔒 请点击【❤️ 捐赠 / 授权】解锁「自动播放视频」', ''); flashDonateBtn(); return; }
autoPlayVideo = autoVideoCheck.checked; syncVideoUI(); saveConfig();
setStatus(`自动播放视频: ${autoPlayVideo ? '开启' : '关闭'}`, '');
addLog(`⚙️ 自动播放视频 ${autoPlayVideo ? '开启' : '关闭'}`, '');
});
speedBtns.forEach(btn => btn.addEventListener('click', () => {
videoSpeed = parseInt(btn.dataset.speed, 10) || 2; syncVideoUI(); saveConfig();
setStatus(`视频倍速已设为 ${videoSpeed}x`, '');
addLog(`⚙️ 视频倍速 ${videoSpeed}x`, '');
}));
syncVideoUI();
document.getElementById('qcc-auto-pick').addEventListener('click', () => {
if (authorized) { if (manualPicking) exitManualPick(); autoPickLeftNav(); }
else { if (manualPicking) exitManualPick(); else enterManualPick(); }
});
document.getElementById('qcc-expandRight').addEventListener('click', () => {
if (!authorized) { setStatus('🔒 请点击【❤️ 捐赠 / 授权】解锁「一键展开」', ''); flashDonateBtn(); return; }
if (expanding) { setStatus('⏳ 正在展开中,请稍候…', 'running'); return; }
expandAllCollapsed();
});
document.getElementById('qcc-clearList').addEventListener('click', () => { clearSelected(); setStatus('元素列表已清空', ''); addLog('🗑️ 已清空选择列表', ''); });
document.getElementById('qcc-donate').addEventListener('click', openDonateModal);
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(); });
document.getElementById('qcc-play-current').addEventListener('click', playCurrentPageVideo);
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', () => {
stopHeartbeat(); if (manualPicking) exitManualPick(); stopClicking();
if (logUpdateTimer) { clearInterval(logUpdateTimer); logUpdateTimer = 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, baseLeftCSS = 0, baseTopCSS = 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();
baseLeftCSS = r.left / z;
baseTopCSS = r.top / z;
panel.style.right = 'auto';
panel.style.left = baseLeftCSS + 'px';
panel.style.top = baseTopCSS + 'px';
e.preventDefault();
});
document.addEventListener('mousemove', e => {
if (!dr) return;
const z = parseFloat(panel.style.zoom) || 1;
const dx = (e.clientX - sx) / z;
const dy = (e.clientY - sy) / z;
let nx = baseLeftCSS + dx;
let ny = baseTopCSS + dy;
const maxX = window.innerWidth / z - panel.offsetWidth;
const maxY = window.innerHeight / z - 40;
nx = Math.max(0, Math.min(maxX, nx));
ny = Math.max(0, Math.min(maxY, ny));
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) { exitManualPick(); return; }
if (isRunning) { stopClicking(); setStatus('ESC 已停止点击', ''); }
}
});
updatePickButton(); renderList(); updateRunButtons();
if (typeof __syncAuthUI === 'function') { try { __syncAuthUI(); } catch (e) {} }
(async () => {
await loadAuthFromStorage();
updatePickButton();
if (typeof __syncAuthUI === 'function') { try { __syncAuthUI(); } catch (e) {} }
setStatus(authorized ? '已授权,可以开始使用' : (authCode ? '⚠️ 上次验证未通过,请重新验证' : '等待操作'), '');
})();
}
function safeBuildPanel() {
try { if (!document.body) { setTimeout(safeBuildPanel, 50); return; } buildPanel(); }
catch (e) { reportError('面板构建失败', e, false); }
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', safeBuildPanel, { once: true });
else safeBuildPanel();
window.addEventListener('resize', () => {
try { applyAutoScale(); } catch (e) {}
});
})();