// ==UserScript== // @name 职业成长刷课脚本 // @namespace https://www.zyczedu.com/ // @version 0.1.2 // @description 自动播放视频、学完后点击“我已学完”,并切换到下一个未完成视频。 // @author 脚本喵 // @match https://www.zyczedu.com/player/study/index* // @match https://www.zyczedu.com/js/player/pages/play.html* // @icon https://jiaobenmiao.com/img/logo2.jpg // @grant none // @run-at document-idle // ==/UserScript== (function () { 'use strict'; const LOOP_MS = 2000; const NEXT_DELAY_MS = 4000; const SUBMIT_COOLDOWN_MS = 8000; const STORAGE_KEY_ENABLED = 'zyczedu-auto-study-enabled'; const STORAGE_KEY_COLLAPSED = 'zyczedu-auto-study-collapsed'; const isMainPage = location.pathname.indexOf('/player/study/index') !== -1; const isPlayerFrame = location.pathname.indexOf('/js/player/pages/play.html') !== -1; function log(message) { console.log('[zyczedu-auto-study]', message); } function isAutomationEnabled() { try { return localStorage.getItem(STORAGE_KEY_ENABLED) !== '0'; } catch (error) { return true; } } function setAutomationEnabled(enabled) { try { localStorage.setItem(STORAGE_KEY_ENABLED, enabled ? '1' : '0'); } catch (error) { void error; } } function isPanelCollapsed() { try { return localStorage.getItem(STORAGE_KEY_COLLAPSED) === '1'; } catch (error) { return false; } } function setPanelCollapsed(collapsed) { try { localStorage.setItem(STORAGE_KEY_COLLAPSED, collapsed ? '1' : '0'); } catch (error) { void error; } } function isVisible(element) { if (!element) { return false; } const style = window.getComputedStyle(element); return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0'; } function parseTimeToSeconds(value) { if (!value) { return 0; } const parts = value.trim().split(':').map(Number); if (parts.some(Number.isNaN)) { return 0; } if (parts.length === 3) { return parts[0] * 3600 + parts[1] * 60 + parts[2]; } if (parts.length === 2) { return parts[0] * 60 + parts[1]; } return parts[0] || 0; } function formatSeconds(value) { const totalSeconds = Math.max(0, Math.floor(Number(value) || 0)); const hours = Math.floor(totalSeconds / 3600); const minutes = Math.floor((totalSeconds % 3600) / 60); const seconds = totalSeconds % 60; return [hours, minutes, seconds] .map(function (part) { return String(part).padStart(2, '0'); }) .join(':'); } function click(element) { if (!element) { return false; } element.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true })); return true; } function getVideosFromDocument(doc) { if (!doc) { return []; } return Array.from(doc.querySelectorAll('video')); } function tryPlayVideo(video) { if (!video) { return; } if (!video.paused && !video.ended) { return; } const playPromise = video.play(); if (playPromise && typeof playPromise.catch === 'function') { playPromise.catch(function () { try { video.muted = true; video.play().catch(function () {}); } catch (error) { void error; } }); } } function pauseVideo(video) { if (!video) { return; } if (!video.paused) { video.pause(); } } function frameVideoHelper() { function tick() { const videos = getVideosFromDocument(document); if (isAutomationEnabled()) { videos.forEach(tryPlayVideo); return; } videos.forEach(pauseVideo); } tick(); window.setInterval(tick, LOOP_MS); } function mainPageHelper() { let lastCourseId = ''; let lastSubmitAt = 0; let lastNextAt = 0; let endedCourseId = ''; let lastAction = '等待开始'; let panelElements = null; function text(selector) { const element = document.querySelector(selector); return element ? element.textContent.trim() : ''; } function value(selector) { const element = document.querySelector(selector); return element ? String(element.value || '').trim() : ''; } function getFrameDocument() { const iframe = document.querySelector('#container'); if (!iframe) { return null; } try { return iframe.contentDocument || (iframe.contentWindow && iframe.contentWindow.document) || null; } catch (error) { return null; } } function getActiveSection() { return document.querySelector('#list_chapter .section.active'); } function getCurrentCourseId() { const active = getActiveSection(); return value('#hiddenCourseId') || (active && active.getAttribute('data-jhx-res')) || ''; } function getCurrentVideo() { const frameDoc = getFrameDocument(); const videos = getVideosFromDocument(frameDoc).concat(getVideosFromDocument(document)); return videos[0] || null; } function getPendingSections() { return Array.from(document.querySelectorAll('#list_chapter .section')).filter(function (section) { return !section.querySelector('.status-done'); }); } function getCurrentTitle() { const title = document.querySelector('.player-wrapper .info .title'); return title ? title.textContent.trim() : '未识别课程'; } function ensurePlayback() { const frameDoc = getFrameDocument(); const videos = getVideosFromDocument(frameDoc).concat(getVideosFromDocument(document)); videos.forEach(tryPlayVideo); } function pausePlayback() { const frameDoc = getFrameDocument(); const videos = getVideosFromDocument(frameDoc).concat(getVideosFromDocument(document)); videos.forEach(pauseVideo); } function setLastAction(message) { if (lastAction === message) { updatePanel(); return; } lastAction = message; log(message); updatePanel(); } function createPanel() { if (panelElements) { return panelElements; } const style = document.createElement('style'); style.textContent = [ '#zy-auto-study-panel { position: fixed; top: 90px; right: 24px; z-index: 999999; width: 240px; padding: 14px; border-radius: 12px; background: rgba(18, 28, 39, 0.92); color: #f3f7fb; box-shadow: 0 12px 30px rgba(0, 0, 0, 0.22); font: 13px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }', '#zy-auto-study-panel.is-collapsed { width: 240px; }', '#zy-auto-study-panel .zy-header { display: flex; align-items: center; justify-content: space-between; gap: 10px; }', '#zy-auto-study-panel .zy-title { margin: 0 0 10px; font-size: 15px; font-weight: 700; }', '#zy-auto-study-panel .zy-collapse { border: 0; border-radius: 8px; padding: 6px 10px; cursor: pointer; color: #f3f7fb; background: rgba(255, 255, 255, 0.12); font-size: 12px; font-weight: 700; }', '#zy-auto-study-panel .zy-toggle { width: 100%; border: 0; border-radius: 8px; padding: 9px 12px; cursor: pointer; color: #fff; font-size: 13px; font-weight: 700; background: #1f9d55; }', '#zy-auto-study-panel .zy-toggle.is-paused { background: #2563eb; }', '#zy-auto-study-panel .zy-grid { margin-top: 12px; display: grid; gap: 8px; }', '#zy-auto-study-panel.is-collapsed .zy-grid, #zy-auto-study-panel.is-collapsed .zy-toggle { display: none; }', '#zy-auto-study-panel.is-collapsed .zy-title { margin-bottom: 0; }', '#zy-auto-study-panel .zy-item { padding: 8px 10px; border-radius: 8px; background: rgba(255, 255, 255, 0.08); }', '#zy-auto-study-panel .zy-label { display: block; margin-bottom: 2px; color: #a9b7c6; font-size: 12px; }', '#zy-auto-study-panel .zy-value { display: block; color: #f8fbff; word-break: break-word; }' ].join(''); document.head.appendChild(style); const panel = document.createElement('div'); panel.id = 'zy-auto-study-panel'; panel.innerHTML = [ '
自动学习面板
', '', '
', '
状态
', '
剩余未学完
', '
当前课程
', '
学习进度
', '
最近动作
', '
' ].join(''); document.body.appendChild(panel); panelElements = { panel: panel, collapse: panel.querySelector('#zy-auto-study-collapse'), toggle: panel.querySelector('#zy-auto-study-toggle'), state: panel.querySelector('#zy-auto-study-state'), pendingCount: panel.querySelector('#zy-auto-study-pending-count'), course: panel.querySelector('#zy-auto-study-course'), progress: panel.querySelector('#zy-auto-study-progress'), action: panel.querySelector('#zy-auto-study-action') }; panelElements.toggle.addEventListener('click', function () { const nextEnabled = !isAutomationEnabled(); setAutomationEnabled(nextEnabled); if (nextEnabled) { setLastAction('已手动开始自动学习'); ensurePlayback(); } else { setLastAction('已手动暂停自动学习'); pausePlayback(); } updatePanel(); }); panelElements.collapse.addEventListener('click', function () { setPanelCollapsed(!isPanelCollapsed()); updatePanel(); }); window.addEventListener('storage', function (event) { if (event.key === STORAGE_KEY_ENABLED || event.key === STORAGE_KEY_COLLAPSED) { updatePanel(); } }); return panelElements; } function updatePanel() { const panel = createPanel(); const enabled = isAutomationEnabled(); const collapsed = isPanelCollapsed(); const pendingCount = getPendingSections().length; const video = getCurrentVideo(); const videoDuration = video && Number.isFinite(video.duration) ? video.duration : 0; const videoCurrentTime = video && Number.isFinite(video.currentTime) ? video.currentTime : 0; const duration = videoDuration > 0 ? formatSeconds(videoDuration) : (text('#durationStr') || '--'); const learned = videoDuration > 0 ? formatSeconds(videoCurrentTime) : (text('#learnedStr') || '--'); panel.panel.classList.toggle('is-collapsed', collapsed); panel.collapse.textContent = collapsed ? '展开' : '隐藏'; panel.toggle.textContent = enabled ? '暂停自动学习' : '开始自动学习'; panel.toggle.classList.toggle('is-paused', !enabled); panel.state.textContent = enabled ? '运行中' : '已暂停'; panel.pendingCount.textContent = String(pendingCount); panel.course.textContent = getCurrentTitle(); panel.progress.textContent = learned + ' / ' + duration; panel.action.textContent = lastAction; } function handleEndedBinding() { const video = getCurrentVideo(); const courseId = getCurrentCourseId(); if (!video || !courseId) { return; } if (video.dataset.autoStudyBound === courseId) { return; } video.dataset.autoStudyBound = courseId; video.addEventListener('ended', function () { endedCourseId = courseId; setLastAction('视频播放结束,准备提交完成'); }); } function handleIntervalModal() { const modal = document.querySelector('#show-time-interval'); const openButton = document.querySelector('#interval-open'); if (isVisible(modal) && openButton) { click(openButton); setLastAction('已自动点击继续打开'); return true; } return false; } function closeProgressTipIfNeeded() { const modal = document.querySelector('#vue_dialog_sub_my'); const closeButton = document.querySelector('#d_sub_confirm_my'); const textNode = document.querySelector('#d_sub_txt_my'); const message = textNode ? textNode.textContent.trim() : ''; if (isVisible(modal) && closeButton && message.indexOf('本视频学习还剩约') !== -1) { endedCourseId = ''; lastSubmitAt = Date.now(); click(closeButton); setLastAction('关闭未学完提示,继续等待进度'); return true; } return false; } function isCurrentDone() { const active = getActiveSection(); const submitButton = document.querySelector('#btn_submit'); const hiddenStatus = value('#hiddenStudyStatus'); const buttonMarkedDone = Boolean( submitButton && submitButton.disabled && submitButton.classList.contains('btn-dark') ); const menuMarkedDone = Boolean(active && active.querySelector('.status-done')); return hiddenStatus === '2' || buttonMarkedDone || menuMarkedDone; } function shouldSubmitCurrent() { if (isCurrentDone()) { return false; } const duration = parseTimeToSeconds(text('#durationStr')); const learned = parseTimeToSeconds(text('#learnedStr')); const courseId = getCurrentCourseId(); const video = getCurrentVideo(); const nearDone = duration > 0 && learned >= Math.max(duration - 1, 0); if (duration > 0 && learned >= duration) { return true; } if (video && video.ended && nearDone) { return true; } if (courseId && endedCourseId === courseId && nearDone) { return true; } return false; } function submitCurrent() { const button = document.querySelector('#btn_submit'); if (!button || button.disabled) { return false; } click(button); lastSubmitAt = Date.now(); setLastAction('已点击“我已学完”'); return true; } function getFirstPendingSection() { const sections = getPendingSections(); if (!sections.length) { return null; } return sections[0]; } function goToFirstPending() { const section = getFirstPendingSection(); if (!section) { setLastAction('全部课程已学完'); return false; } const active = getActiveSection(); if (active === section) { return true; } click(section); lastNextAt = Date.now(); endedCourseId = ''; setLastAction('已定位到第一个未学完课程'); return true; } function ensureFirstPendingSelected() { const firstPending = getFirstPendingSection(); if (!firstPending) { updatePanel(); setLastAction('全部课程已学完'); return true; } const active = getActiveSection(); if (active !== firstPending) { goToFirstPending(); return true; } return false; } function resetWhenCourseChanged() { const courseId = getCurrentCourseId(); if (!courseId || courseId === lastCourseId) { return; } lastCourseId = courseId; endedCourseId = ''; setLastAction('当前课程已切换'); } function tick() { updatePanel(); resetWhenCourseChanged(); if (!isAutomationEnabled()) { pausePlayback(); return; } if (ensureFirstPendingSelected()) { return; } if (handleIntervalModal()) { return; } closeProgressTipIfNeeded(); ensurePlayback(); handleEndedBinding(); if (isCurrentDone()) { if (Date.now() - lastNextAt >= NEXT_DELAY_MS) { goToFirstPending(); } return; } if (shouldSubmitCurrent() && Date.now() - lastSubmitAt >= SUBMIT_COOLDOWN_MS) { submitCurrent(); } } createPanel(); setLastAction(isAutomationEnabled() ? '自动学习已启动' : '自动学习当前已暂停'); tick(); window.setInterval(tick, LOOP_MS); } if (isPlayerFrame) { frameVideoHelper(); return; } if (isMainPage) { mainPageHelper(); } })();