// ==UserScript==
// @name 柠檬文才学习平台自动刷课【全自动】
// @namespace `https://learning.wencaischool.net/`
// @namespace 一心向善
// @version 1.0
// @description 全自动完成刷视频章节,支持自动化下一节【如需答题、考试请联系作者】
// @match *://learning.wencaischool.net/*
// @match *://site.wencaischool.net/*
// @match *://*.wencaischool.net/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
if (window.__studyHelper) {
console.log('🎓 已有脚本在运行,跳过初始化');
return;
}
console.log('🎓 天府文才自动刷课 v1.0 启动!');
console.log('📌 当前URL:', location.href);
const config = {
speed: 2.0,
autoPlay: true,
autoNext: true,
debug: true
};
function log(msg, type = 'info') {
if (config.debug || type !== 'info') {
const prefix = type === 'error' ? '❌' : type === 'success' ? '✅' : type === 'warning' ? '⚠️' : '🔍';
console.log(`${prefix} ${msg}`);
}
}
function createUI() {
const existingPanel = window.top.document.getElementById('study-helper-v11');
if (existingPanel) {
log('⚠️ 主页面已有控制面板,跳过创建', 'warning');
return;
}
if (window !== window.top) {
log('⚠️ 当前是iframe,不创建控制面板', 'warning');
return;
}
const panel = document.createElement('div');
panel.id = 'study-helper-v11';
panel.innerHTML = `
`;
document.body.appendChild(panel);
// 初始化拖拽功能
initDrag();
document.getElementById('speed-sel').addEventListener('change', function() {
config.speed = parseFloat(this.value);
applySpeed();
updateStatus(`速度: ${config.speed}x`, 'success');
});
document.getElementById('autoplay-cb').addEventListener('change', function() {
config.autoPlay = this.checked;
updateStatus(config.autoPlay ? '自动播放 开启' : '自动播放 关闭', 'info');
});
document.getElementById('autonext-cb').addEventListener('change', function() {
config.autoNext = this.checked;
updateStatus(config.autoNext ? '自动切换 开启' : '自动切换 关闭', 'info');
});
log('✅ UI创建成功', 'success');
}
function initDrag() {
const panel = document.getElementById('study-panel-drag');
if (!panel) return;
let isDragging = false;
let startX, startY, panelX, panelY;
panel.addEventListener('mousedown', function(e) {
isDragging = true;
startX = e.clientX;
startY = e.clientY;
panelX = panel.offsetLeft;
panelY = panel.offsetTop;
panel.style.cursor = 'grabbing';
panel.style.zIndex = '999999999';
});
document.addEventListener('mousemove', function(e) {
if (!isDragging) return;
const dx = e.clientX - startX;
const dy = e.clientY - startY;
let newX = panelX + dx;
let newY = panelY + dy;
const maxX = window.innerWidth - panel.offsetWidth;
const maxY = window.innerHeight - panel.offsetHeight;
newX = Math.max(0, Math.min(newX, maxX));
newY = Math.max(0, Math.min(newY, maxY));
panel.style.left = newX + 'px';
panel.style.top = newY + 'px';
panel.style.right = 'auto';
});
document.addEventListener('mouseup', function() {
if (isDragging) {
isDragging = false;
panel.style.cursor = 'move';
}
});
panel.addEventListener('mouseup', function() {
panel.style.cursor = 'move';
});
}
function updateStatus(text, type) {
if (window.top !== window) {
const el = window.top.document.getElementById('status-txt');
if (el) {
el.textContent = text;
el.style.color = type === 'success' ? '#4ade80' : type === 'warning' ? '#fbbf24' : type === 'error' ? '#ef4444' : '#60a5fa';
}
} else {
const el = document.getElementById('status-txt');
if (el) {
el.textContent = text;
el.style.color = type === 'success' ? '#4ade80' : type === 'warning' ? '#fbbf24' : type === 'error' ? '#ef4444' : '#60a5fa';
}
}
log(text, type);
}
function updateInfo(text) {
if (window.top !== window) {
const el = window.top.document.getElementById('info-txt');
if (el) el.textContent = text;
} else {
const el = document.getElementById('info-txt');
if (el) el.textContent = text;
}
}
function updateChapter(text) {
if (window.top !== window) {
const el = window.top.document.getElementById('chapter-txt');
if (el) el.textContent = text;
} else {
const el = document.getElementById('chapter-txt');
if (el) el.textContent = text;
}
}
function findVideo() {
const frames = document.querySelectorAll('iframe');
for (let frame of frames) {
try {
const v = frame.contentDocument.querySelector('video');
if (v) { log('📺 在iframe中找到视频', 'success'); return v; }
} catch (e) {}
}
const videos = document.querySelectorAll('video');
if (videos.length > 0) {
for (let v of videos) if (v.offsetParent !== null || v.style.display !== 'none') return v;
return videos[0];
}
return null;
}
function applySpeed() { const v = findVideo(); if (v) v.playbackRate = config.speed; }
// ============== 改进:更好的章节检测 ==============
function findChapters() {
const allChapters = [];
const frames = document.querySelectorAll('iframe');
for (let frame of frames) {
try {
const doc = frame.contentDocument;
const sections = doc.querySelectorAll('.childSection');
log(`📋 在iframe中找到 ${sections.length} 个.childSection`, 'info');
for (let sec of sections) {
if (!sec.offsetParent) continue;
const text = (sec.textContent || '').trim();
if (!text || !text.includes('应耗能量')) continue;
const isActive = checkIsActive(sec);
// 调试:输出找到的章节
if (isActive) {
log(`✅ 找到当前激活章节: ${text.substring(0, 40)}`, 'success');
}
allChapters.push({
element: sec,
text: text.substring(0, 50),
isActive: isActive
});
}
if (allChapters.length > 0) break;
} catch (e) {}
}
log(`✅ 总共找到 ${allChapters.length} 个章节`, 'success');
return allChapters;
}
// 改进:更好的当前章节检测
function checkIsActive(element) {
// 方法1:检查元素自身或子元素的样式
const children = element.querySelectorAll('*');
for (let child of children) {
const style = window.getComputedStyle(child);
// 检查背景色(蓝色圆点)
if (style.backgroundColor) {
const bg = style.backgroundColor.toLowerCase();
if (bg.includes('blue') ||
bg.includes('rgb(33, 150, 243)') ||
bg.includes('rgb(30, 144, 255)') ||
bg.includes('rgb(0, 122, 255)') ||
bg.includes('rgb(52, 152, 219)') ||
bg.includes('rgb(66, 153, 225)')) {
return true;
}
}
// 检查文字颜色
if (style.color) {
const color = style.color.toLowerCase();
if (color.includes('blue') ||
color.includes('rgb(33, 150, 243)') ||
color.includes('rgb(30, 144, 255)')) {
return true;
}
}
// 检查border颜色(如果圆点是边框实现的)
if (style.borderColor) {
const bc = style.borderColor.toLowerCase();
if (bc.includes('blue') ||
bc.includes('rgb(33, 150, 243)')) {
return true;
}
}
}
// 方法2:检查元素的class是否包含active相关的词
const className = element.className.toLowerCase();
if (className.includes('active') ||
className.includes('current') ||
className.includes('selected') ||
className.includes('playing')) {
return true;
}
// 方法3:检查父元素的class
if (element.parentElement) {
const parentClass = element.parentElement.className.toLowerCase();
if (parentClass.includes('active') ||
parentClass.includes('current')) {
return true;
}
}
// 方法4:检查元素的文本颜色(可能是蓝色高亮)
const selfStyle = window.getComputedStyle(element);
if (selfStyle.color) {
const color = selfStyle.color.toLowerCase();
if (color.includes('blue') ||
color.includes('rgb(33, 150, 243)') ||
color.includes('rgb(30, 144, 255)')) {
return true;
}
}
return false;
}
function findNextChapter() {
const chapters = findChapters();
if (chapters.length === 0) {
updateStatus('未找到章节', 'error');
return null;
}
let currentIndex = -1;
for (let i = 0; i < chapters.length; i++) {
if (chapters[i].isActive) {
currentIndex = i;
log(`📍 找到当前章节: [${i}] ${chapters[i].text.substring(0, 40)}`, 'success');
break;
}
}
if (currentIndex === -1) {
log('⚠️ 未找到当前激活章节', 'warning');
// 改进:不要直接返回第一个,而是保持当前状态
updateStatus('未识别当前章节,请手动选择', 'warning');
return null; // 返回null,不跳转
}
const nextIndex = currentIndex + 1;
if (nextIndex >= chapters.length) {
log('🎉 已完成所有章节!', 'success');
updateStatus('🎉 已完成所有章节!', 'success');
return null;
}
log(`➡️ 下一章: [${nextIndex}] ${chapters[nextIndex].text.substring(0, 40)}`, 'success');
updateChapter(`当前: ${chapters[currentIndex].text.substring(0, 25)}\n下一章: ${chapters[nextIndex].text.substring(0, 25)}`);
return chapters[nextIndex].element;
}
let counter = 0;
let videoFinished = false;
let switchCooldown = false;
function mainLoop() {
counter++;
if (!config.autoNext && !config.autoPlay) {
if (counter % 120 === 0) updateStatus('脚本已暂停', 'warning');
return;
}
const v = findVideo();
if (!v) {
if (counter % 60 === 0) {
updateStatus('未检测到视频', 'warning');
updateInfo('请进入视频播放页面');
}
return;
}
if (Math.abs(v.playbackRate - config.speed) > 0.01) applySpeed();
if (config.autoPlay && v.paused && !v.ended && v.readyState >= 2) {
v.play().catch(e => log('播放失败: ' + e.message, 'error'));
updateStatus('正在播放', 'success');
videoFinished = false;
}
if (v.duration && !isNaN(v.duration)) {
const cur = Math.floor(v.currentTime);
const total = Math.floor(v.duration);
const pct = Math.floor((cur / total) * 100);
const cm = Math.floor(cur / 60), cs = cur % 60, tm = Math.floor(total / 60), ts = total % 60;
updateInfo(`📊 ${pct}% | ${cm}:${String(cs).padStart(2, '0')} / ${tm}:${String(ts).padStart(2, '0')} | ${config.speed}x`);
if (cur >= total - 2 && !videoFinished && config.autoNext && !switchCooldown) {
videoFinished = true;
updateStatus('视频即将完成,准备切换...', 'warning');
setTimeout(() => {
const next = findNextChapter();
if (next) {
updateStatus('点击下一章', 'success');
switchCooldown = true;
next.click();
setTimeout(() => {
switchCooldown = false;
videoFinished = false;
}, 10000);
} else {
updateStatus('未找到下一章', 'error');
}
}, 3000);
}
}
}
function init() {
log('⏳ 初始化...', 'info');
if (window === window.top) {
setTimeout(createUI, 800);
}
setInterval(mainLoop, 500);
updateStatus('脚本已启动', 'success');
}
if (document.readyState === 'complete') {
init();
} else {
window.addEventListener('load', init);
window.addEventListener('DOMContentLoaded', init);
}
window.__studyHelper = {
config: config,
findVideo: findVideo,
findChapters: findChapters,
findNextChapter: findNextChapter,
version: '11.3'
};
})();