// ==UserScript== // @name Dangqi Piaopiao Auto Runner // @namespace https://github.com/ // @version 1.0.1 // @description 自动轮播当前课程视频、跳过提示并提供可配置悬浮面板。 // @author GitHub Copilot // @match https://dangqipiaopiao.*.edu.cn/* // @grant none // @run-at document-idle // ==/UserScript== (function () { 'use strict'; const COOKIE_NAME = 'dqpp_auto_settings'; const COOKIE_MAX_AGE = 60 * 60 * 24 * 365; // 365 天 const PANEL_ID = 'dqpp-auto-panel'; const PANEL_STYLE_ID = 'dqpp-auto-panel-style'; const LOG_MAX_ENTRIES = 60; const ALERT_AUDIO_URL = 'https://cdn.pixabay.com/audio/2023/10/22/audio_107003231b.mp3'; const SELECTORS = { finishModal: 'body > div.public_cont.public_cont1', finishButton: 'div.public_btn > a', introModal: 'div.public_cont:not(.public_cont1)', introSubmit: '.public_submit', progressCancel: '.public_cancel', playerElapsed: 'div.plyr__controls__item:nth-child(2)', playerRemaining: 'div.plyr__controls__item:nth-child(3)', playlistDuration: 'li.video_red1 > span:nth-child(2)', playerContainer: '#wrapper.video_play1 .plyr', playerPoster: '#wrapper.video_play1 .plyr__poster', }; const defaultSettings = { autoPlayNext: false, skipIntro: false, running: false, delayMin: 1, delayMax: 2, panelPosition: { top: '120px', left: '24px', }, }; let settings = loadSettings(); let isRunning = false; let loopTimer = null; let loopBusy = false; let nextClickScheduled = false; let readyToAdvance = false; const logs = []; let pauseResumeAttempts = 0; let pauseResumeBlocked = false; let alertAudio = null; const operationFailureCounter = Object.create(null); const uiState = { panel: null, status: null, runButton: null, stopButton: null, autoNextCheckbox: null, skipIntroCheckbox: null, delayMinInput: null, delayMaxInput: null, logList: null, logEmpty: null, }; whenReady(() => { injectPanelStyle(); buildPanel(); applySettingsToPanel(); prepareAlertAudio(); if (settings.running) { startAutomation(true); } else { setStatusMessage('待命'); } }); function whenReady(callback) { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', callback, { once: true }); } else { callback(); } } function injectPanelStyle() { if (document.getElementById(PANEL_STYLE_ID)) return; const style = document.createElement('style'); style.id = PANEL_STYLE_ID; style.textContent = ` #${PANEL_ID} { position: fixed; top: ${settings.panelPosition.top}; left: ${settings.panelPosition.left}; width: 260px; background: rgba(17, 24, 39, 0.95); color: #f8fafc; font-family: 'Segoe UI', 'PingFang SC', sans-serif; font-size: 12px; border-radius: 10px; box-shadow: 0 15px 35px rgba(15, 23, 42, 0.35); z-index: 600000; backdrop-filter: blur(6px); line-height: 1.5; user-select: none; } #${PANEL_ID} header { display: flex; align-items: center; justify-content: space-between; padding: 10px 14px; cursor: move; font-weight: 600; border-bottom: 1px solid rgba(148, 163, 184, 0.25); } #${PANEL_ID} header span { font-size: 13px; } #${PANEL_ID} .dqpp-status { font-size: 11px; color: #22d3ee; } #${PANEL_ID} main { padding: 12px 14px 14px; display: flex; flex-direction: column; gap: 10px; } #${PANEL_ID} label { display: flex; align-items: center; gap: 6px; cursor: pointer; } #${PANEL_ID} input[type="checkbox"] { accent-color: #38bdf8; } #${PANEL_ID} .dqpp-field { display: flex; gap: 6px; align-items: center; } #${PANEL_ID} .dqpp-field input[type="number"] { flex: 1; width: 70px; padding: 4px 6px; border: 1px solid rgba(148, 163, 184, 0.4); border-radius: 6px; background: rgba(15, 23, 42, 0.7); color: #f1f5f9; outline: none; } #${PANEL_ID} .dqpp-field input[type="number"]:focus { border-color: #38bdf8; box-shadow: 0 0 0 2px rgba(56, 189, 248, 0.2); } #${PANEL_ID} footer { display: flex; gap: 10px; padding: 10px 14px 14px; } #${PANEL_ID} button { flex: 1; padding: 8px 0; border-radius: 6px; border: none; cursor: pointer; font-weight: 600; font-size: 12px; transition: transform 0.15s ease, box-shadow 0.15s ease; } #${PANEL_ID} button:active { transform: translateY(1px); } #${PANEL_ID} .dqpp-btn-run { background: linear-gradient(135deg, #38bdf8, #22d3ee); color: #0f172a; box-shadow: 0 10px 20px rgba(34, 211, 238, 0.25); } #${PANEL_ID} .dqpp-btn-stop { background: linear-gradient(135deg, #f43f5e, #fb7185); color: #fff1f2; box-shadow: 0 10px 20px rgba(244, 63, 94, 0.25); } #${PANEL_ID} .dqpp-note { font-size: 11px; color: rgba(226, 232, 240, 0.7); } #${PANEL_ID} .dqpp-test-audio { padding: 4px 8px; font-size: 11px; border-radius: 4px; border: 1px solid rgba(148, 163, 184, 0.4); background: rgba(15, 23, 42, 0.7); color: #f1f5f9; cursor: pointer; transition: all 0.15s ease; } #${PANEL_ID} .dqpp-test-audio:hover { background: rgba(59, 130, 246, 0.2); border-color: #3b82f6; } #${PANEL_ID} .dqpp-log { display: flex; flex-direction: column; gap: 6px; border-top: 1px solid rgba(148, 163, 184, 0.2); padding-top: 8px; max-height: 160px; } #${PANEL_ID} .dqpp-log-title { font-size: 11px; font-weight: 600; letter-spacing: 0.02em; color: rgba(148, 197, 253, 0.9); } #${PANEL_ID} .dqpp-log-list { flex: 1; overflow-y: auto; padding-right: 4px; display: flex; flex-direction: column; gap: 4px; scrollbar-width: thin; } #${PANEL_ID} .dqpp-log-list::-webkit-scrollbar { width: 6px; } #${PANEL_ID} .dqpp-log-list::-webkit-scrollbar-thumb { background: rgba(148, 163, 184, 0.25); border-radius: 4px; } #${PANEL_ID} .dqpp-log-entry { display: flex; gap: 6px; align-items: flex-start; font-size: 11px; line-height: 1.4; color: rgba(226, 232, 240, 0.85); } #${PANEL_ID} .dqpp-log-entry time { font-variant-numeric: tabular-nums; color: rgba(148, 163, 184, 0.8); flex-shrink: 0; } #${PANEL_ID} .dqpp-log-empty { font-size: 11px; color: rgba(148, 163, 184, 0.6); } `; document.head.appendChild(style); } function buildPanel() { if (document.getElementById(PANEL_ID)) { uiState.panel = document.getElementById(PANEL_ID); return; } const panel = document.createElement('section'); panel.id = PANEL_ID; panel.innerHTML = `
视频助手 ...
随机延迟 (秒)
~

随机延迟将在所有自动点击时生效,默认 1~2 秒。

操作记录
暂无日志
`; document.body.appendChild(panel); uiState.panel = panel; uiState.status = panel.querySelector('.dqpp-status'); uiState.runButton = panel.querySelector('.dqpp-btn-run'); uiState.stopButton = panel.querySelector('.dqpp-btn-stop'); uiState.logList = panel.querySelector('.dqpp-log-list'); uiState.logEmpty = panel.querySelector('.dqpp-log-empty'); const controls = panel.querySelectorAll('[data-opt]'); controls.forEach((el) => { const key = el.getAttribute('data-opt'); if (key === 'autoPlayNext') uiState.autoNextCheckbox = el; if (key === 'skipIntro') uiState.skipIntroCheckbox = el; if (key === 'delayMin') uiState.delayMinInput = el; if (key === 'delayMax') uiState.delayMaxInput = el; el.addEventListener('change', () => handlePanelInputChange(el, key)); }); uiState.runButton.addEventListener('click', () => startAutomation()); uiState.stopButton.addEventListener('click', () => stopAutomation()); // 添加测试音频按钮事件监听器 const testAudioButton = panel.querySelector('.dqpp-test-audio'); if (testAudioButton) { testAudioButton.addEventListener('click', () => { playAlertSound('测试提示音播放'); }); } makePanelDraggable(panel.querySelector('header')); renderLogs(); } function makePanelDraggable(handle) { if (!handle || !uiState.panel) return; let isDragging = false; let offsetX = 0; let offsetY = 0; handle.addEventListener('mousedown', (event) => { if (event.button !== 0) return; isDragging = true; const rect = uiState.panel.getBoundingClientRect(); offsetX = event.clientX - rect.left; offsetY = event.clientY - rect.top; document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp, { once: true }); }); function onMouseMove(event) { if (!isDragging) return; const newLeft = Math.max(0, event.clientX - offsetX); const newTop = Math.max(0, event.clientY - offsetY); uiState.panel.style.left = `${newLeft}px`; uiState.panel.style.top = `${newTop}px`; } function onMouseUp() { isDragging = false; document.removeEventListener('mousemove', onMouseMove); savePanelPosition(); } } function handlePanelInputChange(element, key) { if (!key) return; let value; if (element.type === 'checkbox') { value = element.checked; } else if (element.type === 'number') { value = element.value; } else { value = element.value; } if (key === 'delayMin' || key === 'delayMax') { const numeric = clampDelayValue(parseFloat(value)); element.value = numeric; value = numeric; } settings = { ...settings, [key]: value, }; ensureDelayOrder(); saveSettings(); logSettingChange(key); if (key === 'skipIntro' && settings.skipIntro) { setStatusMessage('已开启跳过片头'); } } function applySettingsToPanel() { if (!uiState.panel) return; uiState.panel.style.top = settings.panelPosition.top; uiState.panel.style.left = settings.panelPosition.left; if (uiState.autoNextCheckbox) { uiState.autoNextCheckbox.checked = Boolean(settings.autoPlayNext); } if (uiState.skipIntroCheckbox) { uiState.skipIntroCheckbox.checked = Boolean(settings.skipIntro); } if (uiState.delayMinInput) { uiState.delayMinInput.value = settings.delayMin; } if (uiState.delayMaxInput) { uiState.delayMaxInput.value = settings.delayMax; } updateRunButtons(); renderLogs(); } function updateRunButtons() { if (!uiState.runButton || !uiState.stopButton) return; uiState.runButton.disabled = isRunning; uiState.stopButton.disabled = !isRunning; } function setStatusMessage(message, options) { if (uiState.status) uiState.status.textContent = message; if (!message) return; const { log = true, type = 'info' } = options || {}; if (log) { appendLog(message, type); } } function appendLog(message, type = 'info') { const entry = { id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, timestamp: new Date(), message: String(message), type, }; logs.push(entry); while (logs.length > LOG_MAX_ENTRIES) { logs.shift(); } renderLogs(); } function renderLogs() { if (!uiState.logList) return; uiState.logList.innerHTML = ''; if (!logs.length) { if (uiState.logEmpty) uiState.logEmpty.style.display = 'block'; return; } if (uiState.logEmpty) uiState.logEmpty.style.display = 'none'; const fragment = document.createDocumentFragment(); for (const entry of logs) { const row = document.createElement('div'); row.className = `dqpp-log-entry dqpp-log-entry--${entry.type}`; row.dataset.entryId = entry.id; const timeEl = document.createElement('time'); timeEl.textContent = formatLogTimestamp(entry.timestamp); const messageEl = document.createElement('span'); messageEl.textContent = entry.message; row.appendChild(timeEl); row.appendChild(messageEl); fragment.appendChild(row); } uiState.logList.appendChild(fragment); uiState.logList.scrollTop = uiState.logList.scrollHeight; } function formatLogTimestamp(date) { if (!(date instanceof Date)) return ''; const pad = (value) => String(value).padStart(2, '0'); const hours = pad(date.getHours()); const minutes = pad(date.getMinutes()); const seconds = pad(date.getSeconds()); return `${hours}:${minutes}:${seconds}`; } function startAutomation(isAutoStart = false) { if (isRunning) return; isRunning = true; settings.running = true; saveSettings(); updateRunButtons(); setStatusMessage(isAutoStart ? '自动恢复运行中…' : '运行中'); if (loopTimer) clearInterval(loopTimer); loopTimer = setInterval(runAutomationTick, 1500); runAutomationTick(); } function stopAutomation() { if (!isRunning) return; isRunning = false; settings.running = false; saveSettings(); updateRunButtons(); setStatusMessage('已停止'); if (loopTimer) { clearInterval(loopTimer); loopTimer = null; } } function runAutomationTick() { if (!isRunning || loopBusy) return; loopBusy = true; try { if (settings.skipIntro) { handleIntroModal(); } const handledFinish = handleFinishModal(); if (!handledFinish) { handlePausedPlayback(); if (settings.autoPlayNext) { maybeScheduleNextVideo(); } } } finally { loopBusy = false; } } function handleIntroModal() { const introModal = document.querySelector(SELECTORS.introModal); if (!introModal) return false; if (introModal.dataset.dqppSkipHandled === '1') return true; const submitButton = introModal.querySelector(SELECTORS.introSubmit); if (!submitButton) return false; introModal.dataset.dqppSkipHandled = '1'; const delay = randomDelayMs(); setStatusMessage(`检测到片头提示,将在 ${(delay / 1000).toFixed(1)}s 内跳过…`); setTimeout(() => { if (!settings.skipIntro) return; safeClick(submitButton); setStatusMessage('已尝试跳过片头'); }, delay); return true; } function handleFinishModal() { const finishModal = document.querySelector(SELECTORS.finishModal); if (!finishModal) { readyToAdvance = false; return false; } const handledState = finishModal.dataset.dqppFinishHandled; if (handledState && handledState !== 'closing' && !isElementVisible(finishModal)) { delete finishModal.dataset.dqppFinishHandled; } if (handledState === 'closing') { return true; } if (finishModal.dataset.dqppFinishHandled === '1') { return true; } if (!isElementVisible(finishModal)) { return false; } const finishButton = finishModal.querySelector(SELECTORS.finishButton); const cancelButton = finishModal.querySelector(SELECTORS.progressCancel); const modalType = classifyPlaybackModal(finishModal); const isFinished = modalType === 'finished'; finishModal.dataset.dqppFinishHandled = '1'; readyToAdvance = isFinished; const actionButton = modalType === 'progress' && cancelButton ? cancelButton : finishButton; if (!actionButton) return false; const delayBase = isFinished ? 2000 : 500; const delay = delayBase + randomDelayMs(); const nextTarget = isFinished && settings.autoPlayNext ? getNextVideoUrl() : null; switch (modalType) { case 'finished': setStatusMessage(`检测到播放完成,将在 ${(delay / 1000).toFixed(1)}s 后关闭提示…`); break; case 'intro': setStatusMessage(`检测到播放须知,${(delay / 1000).toFixed(1)}s 后继续播放`); break; case 'paused': setStatusMessage(`检测到自动暂停提示,${(delay / 1000).toFixed(1)}s 后继续`); break; case 'progress': setStatusMessage(`检测到进度提示,${(delay / 1000).toFixed(1)}s 后关闭`); break; default: setStatusMessage(`检测到提示窗口,${(delay / 1000).toFixed(1)}s 后确认`); } setTimeout(() => { safeClick(actionButton); finishModal.dataset.dqppFinishHandled = 'closing'; if (isFinished && isRunning && settings.autoPlayNext) { if (nextTarget) { scheduleNextVideoNavigation(nextTarget); } else { setStatusMessage('全部视频已播放完毕'); readyToAdvance = false; nextClickScheduled = false; } } else if (!isFinished) { if (modalType === 'progress') { setStatusMessage('已关闭进度提示'); } else { setStatusMessage('已尝试恢复播放'); } } setTimeout(() => { if (finishModal && finishModal.dataset) { delete finishModal.dataset.dqppFinishHandled; } }, 2000); }, delay); return true; } function handlePausedPlayback() { const player = document.querySelector(SELECTORS.playerContainer); if (!player) return false; const isPaused = player.classList.contains('plyr--paused'); const isPlaying = player.classList.contains('plyr--playing'); if (isPlaying || !isPaused) { pauseResumeAttempts = 0; pauseResumeBlocked = false; markOperationResult('autoResume', true); if (player.dataset.dqppResumeHandled) { delete player.dataset.dqppResumeHandled; } return false; } if (pauseResumeBlocked || player.dataset.dqppResumeHandled === 'blocked') { return false; } const finishModal = document.querySelector(SELECTORS.finishModal); if (finishModal && isElementVisible(finishModal)) { return false; } if (player.dataset.dqppResumeHandled === '1') { return true; } if (pauseResumeAttempts >= 3) { pauseResumeBlocked = true; player.dataset.dqppResumeHandled = 'blocked'; setStatusMessage('自动续播已尝试 3 次未成功,停止自动恢复', { type: 'warn' }); markOperationResult('autoResume', false, '自动续播尝试 3 次仍未成功'); return false; } pauseResumeAttempts += 1; const delay = randomDelayMs(); setStatusMessage( `检测到暂停,第 ${pauseResumeAttempts} 次将在 ${(delay / 1000).toFixed(1)}s 后尝试继续播放`, ); player.dataset.dqppResumeHandled = '1'; setTimeout(() => { const livePlayer = document.querySelector(SELECTORS.playerContainer); if (!isRunning || !livePlayer) { if (player.dataset) { delete player.dataset.dqppResumeHandled; } return; } if (!livePlayer.classList.contains('plyr--paused')) { delete livePlayer.dataset.dqppResumeHandled; pauseResumeAttempts = 0; pauseResumeBlocked = false; markOperationResult('autoResume', true); return; } focusPlayerArea(); const focused = document.activeElement && document.activeElement !== document.body ? document.activeElement : livePlayer; simulateSpacebarPress(focused); const videoEl = livePlayer.querySelector('video'); if (videoEl && typeof videoEl.play === 'function') { videoEl.play().catch(() => { // ignore play promise rejection }); } setStatusMessage('已尝试继续播放(聚焦+空格)'); // 延迟检查播放是否成功 setTimeout(() => { if (!isRunning) return; const currentPlayer = document.querySelector(SELECTORS.playerContainer); if (!currentPlayer) return; // 检查是否仍然暂停 if (currentPlayer.classList.contains('plyr--paused')) { // 继续播放失败,记录失败 markOperationResult('autoResume', false, '自动续播操作未能恢复播放'); } else { // 成功恢复播放,重置计数器和状态 pauseResumeAttempts = 0; pauseResumeBlocked = false; markOperationResult('autoResume', true); setStatusMessage('播放已恢复'); } }, 1500); setTimeout(() => { if (livePlayer && livePlayer.dataset) { delete livePlayer.dataset.dqppResumeHandled; } }, 2000); }, delay); return true; } function maybeScheduleNextVideo() { if (!settings.autoPlayNext || nextClickScheduled) return false; if (!readyToAdvance) return false; const target = getNextVideoUrl(); if (!target) { setStatusMessage('全部视频已播放完毕'); readyToAdvance = false; return false; } scheduleNextVideoNavigation(target); return true; } function scheduleNextVideoNavigation(target) { const { url, label } = target; const delay = randomDelayMs(); setStatusMessage(`将在 ${(delay / 1000).toFixed(1)}s 后跳转:${label}`); nextClickScheduled = true; readyToAdvance = false; setTimeout(() => { if (!isRunning || !settings.autoPlayNext) { nextClickScheduled = false; return; } setStatusMessage(`正在跳转:${label}`); window.location.href = url; nextClickScheduled = false; }, delay); } function classifyPlaybackModal(modal) { const message = getNormalizedText(modal.querySelector('.public_text > p:nth-child(2)')); const finishedKey = '当前视频播放完毕'; const introKey = '您需要完整观看一遍课程视频,才能获取本课学时,看到视频播放完毕提示框即为完成,然后视频可以拖动播放。'; if (modal.querySelector(SELECTORS.progressCancel)) { return 'progress'; } const remainingSeconds = parseDurationToSeconds(getNormalizedText(document.querySelector(SELECTORS.playerRemaining))); if (message.includes(finishedKey) && (remainingSeconds === 0 || remainingSeconds === null)) { return 'finished'; } if (message.includes(introKey)) { const elapsedSeconds = parseDurationToSeconds(getNormalizedText(document.querySelector(SELECTORS.playerElapsed))); const playlistSeconds = parseDurationToSeconds(getNormalizedText(document.querySelector(SELECTORS.playlistDuration))); if (elapsedSeconds !== null && playlistSeconds !== null && Math.abs(elapsedSeconds - playlistSeconds) <= 1) { return 'intro'; } } return 'paused'; } function getNextVideoUrl() { try { const currentUrl = new URL(window.location.href); const ridRaw = currentUrl.searchParams.get('r_id'); if (!ridRaw) { console.warn('[DQPP] 当前链接缺少 r_id 参数,无法计算下一节'); return null; } const rid = Number.parseInt(ridRaw, 10); if (!Number.isFinite(rid)) { console.warn('[DQPP] r_id 参数不是数字,无法计算下一节'); return null; } const nextRid = rid + 1; currentUrl.searchParams.set('r_id', String(nextRid)); return { url: currentUrl.toString(), label: `r_id=${nextRid}`, }; } catch (error) { console.error('[DQPP] 生成下一节链接时出错:', error); return null; } } function getNormalizedText(element) { if (!element) return ''; return (element.textContent || element.innerText || '').replace(/\s+/g, ' ').trim(); } function parseDurationToSeconds(text) { if (!text) return null; const cleaned = text.replace(/[^0-9:]/g, ''); if (!cleaned) return null; const parts = cleaned.split(':').map((part) => part.trim()).filter(Boolean); if (!parts.length) return null; let seconds = 0; let multiplier = 1; for (let i = parts.length - 1; i >= 0; i -= 1) { const value = Number.parseInt(parts[i], 10); if (Number.isNaN(value)) return null; seconds += value * multiplier; multiplier *= 60; } return seconds; } function clampDelayValue(value) { if (Number.isNaN(value) || value < 0) return 0; return Math.round(value * 10) / 10; } function ensureDelayOrder() { let { delayMin, delayMax } = settings; delayMin = clampDelayValue(parseFloat(delayMin)); delayMax = clampDelayValue(parseFloat(delayMax)); if (delayMax < delayMin) { [delayMin, delayMax] = [delayMax, delayMin]; } if (delayMin === delayMax) { delayMax = clampDelayValue(delayMin + 0.1); } settings.delayMin = delayMin; settings.delayMax = delayMax; if (uiState.delayMinInput) uiState.delayMinInput.value = delayMin; if (uiState.delayMaxInput) uiState.delayMaxInput.value = delayMax; } function randomDelayMs() { ensureDelayOrder(); const min = Math.max(0, Number(settings.delayMin) || 0); const max = Math.max(min, Number(settings.delayMax) || min + 0.1); const randomSeconds = Math.random() * (max - min) + min; return Math.round(randomSeconds * 1000); } function isElementVisible(element) { if (!element) return false; const rect = element.getBoundingClientRect(); const style = window.getComputedStyle(element); if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') { return false; } return rect.width > 0 && rect.height > 0; } function safeClick(element) { if (!element) return false; const eventOptions = { bubbles: true, cancelable: true, view: window }; try { element.dispatchEvent(new MouseEvent('mouseover', eventOptions)); element.dispatchEvent(new MouseEvent('mousedown', eventOptions)); element.dispatchEvent(new MouseEvent('mouseup', eventOptions)); element.dispatchEvent(new MouseEvent('click', eventOptions)); if (typeof element.click === 'function') { element.click(); } return true; } catch (error) { console.error('[DQPP] 触发点击时出错:', error); return false; } } function simulateSpacebarPress(target) { const defaultTarget = document.body || window; const eventTarget = target || defaultTarget; const eventOptions = { key: ' ', code: 'Space', keyCode: 32, which: 32, bubbles: true, cancelable: true, }; try { eventTarget.dispatchEvent(new KeyboardEvent('keydown', eventOptions)); eventTarget.dispatchEvent(new KeyboardEvent('keypress', eventOptions)); eventTarget.dispatchEvent(new KeyboardEvent('keyup', eventOptions)); } catch (error) { console.error('[DQPP] 触发空格键时出错:', error); } } function logSettingChange(key) { switch (key) { case 'autoPlayNext': appendLog(`结束后自动播放:${settings.autoPlayNext ? '开启' : '关闭'}`, 'config'); break; case 'skipIntro': appendLog(`跳过片头提示:${settings.skipIntro ? '开启' : '关闭'}`, 'config'); break; case 'delayMin': case 'delayMax': appendLog( `随机延迟范围:${Number(settings.delayMin).toFixed(1)} ~ ${Number(settings.delayMax).toFixed(1)} 秒`, 'config', ); break; default: break; } } function loadSettings() { try { const raw = getCookieValue(COOKIE_NAME); if (!raw) return { ...defaultSettings }; const parsed = JSON.parse(decodeURIComponent(raw)); return { ...defaultSettings, ...parsed }; } catch (error) { console.warn('[DQPP] 读取设置失败,使用默认值', error); return { ...defaultSettings }; } } function saveSettings() { const serialisable = { ...settings, panelPosition: { top: settings.panelPosition.top, left: settings.panelPosition.left, }, }; setCookie(COOKIE_NAME, JSON.stringify(serialisable), COOKIE_MAX_AGE); } function savePanelPosition() { if (!uiState.panel) return; settings.panelPosition = { top: uiState.panel.style.top || `${uiState.panel.offsetTop}px`, left: uiState.panel.style.left || `${uiState.panel.offsetLeft}px`, }; saveSettings(); } function setCookie(name, value, maxAgeSeconds) { if (!name) return; const encoded = encodeURIComponent(value); const cookie = `${name}=${encoded}; max-age=${maxAgeSeconds}; path=/; SameSite=Lax`; document.cookie = cookie; } function getCookieValue(name) { if (!name) return null; const cookies = document.cookie ? document.cookie.split('; ') : []; for (const chunk of cookies) { const [key, ...rest] = chunk.split('='); if (key === name) { return rest.join('='); } } return null; } function focusPlayerArea() { const centerX = Math.round(window.innerWidth / 2); const centerY = Math.round(window.innerHeight / 2); // 直接在页面中心点创建点击事件,不依赖特定元素 const eventOptions = { bubbles: true, cancelable: true, view: window, clientX: centerX, clientY: centerY, screenX: centerX, screenY: centerY }; try { // 在页面中心点触发点击事件 document.elementFromPoint(centerX, centerY)?.focus?.(); document.dispatchEvent(new MouseEvent('click', eventOptions)); // 确保页面获得焦点 if (document.body) { document.body.focus(); } window.focus(); } catch (error) { console.warn('[DQPP] 聚焦页面中心时出错:', error); // 降级方案:尝试点击播放器容器 const fallbackTarget = document.querySelector(SELECTORS.playerContainer) || document.body; safeClick(fallbackTarget); } } function prepareAlertAudio() { if (alertAudio) return alertAudio; if (!ALERT_AUDIO_URL) { console.warn('[DQPP] 音频URL未配置'); return null; } try { alertAudio = new Audio(ALERT_AUDIO_URL); alertAudio.preload = 'auto'; alertAudio.volume = 0.7; // 添加事件监听器来调试音频加载状态 alertAudio.addEventListener('canplaythrough', () => { console.log('[DQPP] 提示音加载完成'); }); alertAudio.addEventListener('error', (e) => { console.error('[DQPP] 提示音加载失败:', e); alertAudio = null; }); console.log('[DQPP] 提示音初始化完成,URL:', ALERT_AUDIO_URL); return alertAudio; } catch (error) { console.warn('[DQPP] 初始化提示音失败:', error); alertAudio = null; return null; } } function playAlertSound(contextMessage) { const audio = prepareAlertAudio(); if (!audio) { console.warn('[DQPP] 无法获取音频实例'); appendLog(`提示音播放失败:音频未初始化`, 'warn'); return; } try { audio.currentTime = 0; const playPromise = audio.play(); if (playPromise && typeof playPromise.catch === 'function') { playPromise.then(() => { console.log('[DQPP] 提示音播放成功'); if (contextMessage) { appendLog(`🔊 ${contextMessage}`, 'warn'); } }).catch((error) => { console.warn('[DQPP] 播放提示音失败:', error); appendLog(`提示音播放失败:${error.message}`, 'warn'); }); } else { console.log('[DQPP] 提示音播放(同步模式)'); if (contextMessage) { appendLog(`🔊 ${contextMessage}`, 'warn'); } } } catch (error) { console.warn('[DQPP] 尝试播放提示音时出错:', error); appendLog(`提示音播放异常:${error.message}`, 'warn'); } } function markOperationResult(operationKey, succeeded, alertMessage) { if (!operationKey) return; if (succeeded) { operationFailureCounter[operationKey] = 0; return; } const nextValue = (operationFailureCounter[operationKey] || 0) + 1; operationFailureCounter[operationKey] = nextValue; // 每次达到3次失败就播放提示音,但不重置计数器 if (nextValue % 3 === 0) { playAlertSound(alertMessage || `操作 ${operationKey} 已连续失败 ${nextValue} 次`); } } })();