// ==UserScript==
// @name 律师云学院助手
// @namespace https://github.com/shiba2046/greasemonkey-scripts
// @version 0.8
// @description 自动刷律协培训课程,支持自动切换章节播放
// @author Pengus
// @match https://lawschool.lawyerpass.com/course/*
// @match https://lawschool.lawyerpass.com/center/*
// @icon https://lawschool.lawyerpass.com/assets/images/favicon.ico
// @grant none
// @license MIT
// ==/UserScript==
// --- 配置区 ---
const CONFIG = {
CHECK_INTERVAL_MS: 5000, // 检查间隔(毫秒)
COURSE_CHECK_INTERVAL_MS: 5000, // 课程内检查间隔(毫秒)
COMPLETION_THRESHOLD: 99, // 完成阈值(百分比)
ELEMENT_WAIT_TIMEOUT: 10000, // 等待元素超时时间(毫秒)
ELEMENT_CHECK_INTERVAL: 500, // 检查元素间隔(毫秒)
DEBUG: true // 调试模式
};
// --- 功能说明 ---
// 1. 自动播放课程视频
// 2. 自动检测并处理弹窗
// 3. 自动监控播放进度
// 4. 自动切换到下一个未完成的章节
// 5. 所有章节完成后自动进入下一课程
// 6. 显示课程进度和章节完成情况
// --- 工具函数 ---
const $ = {
// 查找单个元素(同步)
get: (selector) => document.querySelector(selector),
// 查找多个元素(同步)
getAll: (selector) => document.querySelectorAll(selector),
// 等待元素出现(异步)
waitForElement: async (selector, timeout = CONFIG.ELEMENT_WAIT_TIMEOUT) => {
const startTime = Date.now();
while (Date.now() - startTime < timeout) {
const element = document.querySelector(selector);
if (element) {
$.log(`找到元素: ${selector}`);
return element;
}
await new Promise(resolve => setTimeout(resolve, CONFIG.ELEMENT_CHECK_INTERVAL));
}
$.log(`等待元素超时: ${selector}`);
return null;
},
// 等待多个元素出现(异步)
waitForElements: async (selector, timeout = CONFIG.ELEMENT_WAIT_TIMEOUT) => {
const startTime = Date.now();
while (Date.now() - startTime < timeout) {
const elements = document.querySelectorAll(selector);
if (elements.length > 0) {
$.log(`找到${elements.length}个元素: ${selector}`);
return elements;
}
await new Promise(resolve => setTimeout(resolve, CONFIG.ELEMENT_CHECK_INTERVAL));
}
$.log(`等待元素超时: ${selector}`);
return [];
},
// 等待多个元素并转换为数组(异步)
waitForElementsArray: async (selector, timeout = CONFIG.ELEMENT_WAIT_TIMEOUT) => {
const elements = await $.waitForElements(selector, timeout);
return Array.from(elements || []);
},
// XPath查询
xpath: (xpath) => document.evaluate(xpath, document, null, XPathResult.ANY_TYPE, null).iterateNext(),
// 检查URL是否包含文本
urlHas: (text) => document.location.href.includes(text),
// 日志输出
log: (...args) => CONFIG.DEBUG && console.log('[律师云学院助手]', ...args)
};
// --- 视频控制 ---
const VideoControl = {
// 当前正在处理的章节索引
currentChapterIndex: 0,
// 播放视频
async play() {
const playBtn = await $.waitForElement(".prism-play-btn");
if (playBtn && !$.get(".prism-play-btn.playing")) {
$.log('开始播放视频');
playBtn.click();
return true;
}
return false;
},
// 处理弹窗
async handlePopup() {
const confirmBtn = await $.waitForElement(".ant-modal-confirm-btns > button");
if (confirmBtn) {
$.log('关闭弹窗');
confirmBtn.click();
await this.play();
return true;
}
return false;
},
// 获取所有进度元素
async getAllProgressElements() {
return await $.waitForElementsArray('div.name.pull-left > div.ng-star-inserted');
},
// 获取特定章节的进度
async getChapterProgress(index = this.currentChapterIndex) {
const progressElements = await this.getAllProgressElements();
if (progressElements.length === 0 || !progressElements[index]) return 0;
const progressText = progressElements[index].innerText.split(':')[1];
return parseInt(progressText) || 0;
},
// 获取当前章节的进度
async getProgress() {
return await this.getChapterProgress();
},
// 获取所有章节元素
async getChapterElements() {
return await $.waitForElementsArray('.list-wrapper .list-box');
},
// 点击特定的章节
async clickChapter(index) {
const chapterElements = await this.getChapterElements();
if (chapterElements.length > index) {
$.log(`点击章节 ${index + 1}`);
chapterElements[index].click();
this.currentChapterIndex = index;
return true;
}
return false;
},
// 获取所有章节的进度
async getAllChapterProgress() {
const progressElements = await this.getAllProgressElements();
const results = [];
for (let i = 0; i < progressElements.length; i++) {
const progressText = progressElements[i].innerText.split(':')[1];
const progress = parseInt(progressText) || 0;
results.push({
index: i,
progress: progress,
completed: progress >= CONFIG.COMPLETION_THRESHOLD
});
}
return results;
},
// 检查完成状态并处理多章节
async checkCompletion() {
const progressElements = await this.getAllProgressElements();
if (progressElements.length === 0) return false;
// 获取当前章节进度
const currentProgress = await this.getChapterProgress();
$.log(`当前章节 ${this.currentChapterIndex + 1}/${progressElements.length} 进度: ${currentProgress}%`);
// 如果当前章节完成
if (currentProgress >= CONFIG.COMPLETION_THRESHOLD) {
// 获取所有章节的进度状态
const allProgress = await this.getAllChapterProgress();
// 查找下一个未完成的章节
const nextIncomplete = allProgress.find(item =>
!item.completed && item.index > this.currentChapterIndex
);
// 如果找到了下一个未完成的章节,切换到它
if (nextIncomplete) {
$.log(`当前章节已完成,切换到章节 ${nextIncomplete.index + 1}/${progressElements.length}`);
await this.clickChapter(nextIncomplete.index);
setTimeout(() => this.play(), 1000); // 短暂延迟后播放视频
return true;
}
// 如果所有章节都完成,点击下一步
const allCompleted = allProgress.every(item => item.completed);
if (allCompleted) {
const entranceBtn = await $.waitForElement('.entrance');
if (entranceBtn) {
$.log('所有章节完成,点击下一步');
entranceBtn.click();
return true;
}
}
}
return false;
},
// 更新标题显示当前进度
async updateTitle() {
const titleEl = await $.waitForElement(".title");
if (!titleEl) return;
const allProgress = await this.getAllChapterProgress();
if (allProgress.length === 0) return;
const currentProgress = await this.getProgress();
const courseName = titleEl.textContent.trim().split(' ')[0];
// 计算总体完成情况
const completedChapters = allProgress.filter(item => item.completed).length;
const totalChapters = allProgress.length;
document.title = `${currentProgress}% - [${completedChapters}/${totalChapters}] ${courseName}`;
// 在控制台输出所有章节的进度情况
if (CONFIG.DEBUG) {
console.table(allProgress.map(item => ({
章节号: item.index + 1,
进度: item.progress + '%',
完成: item.completed ? '✓' : '✗'
})));
}
}
};
// --- 课程列表控制 ---
const CourseList = {
// 显示统计信息
async showStats() {
const username = await $.waitForElement('.username');
if (!username) return;
const done = (await $.waitForElements('.text-green')).length;
const notDone = (await $.waitForElements('.text-yellow')).length;
const total = done + notDone;
const statsHtml = `<br/> 共 ${total} 课,已完成 ${done} 课,未完成 ${notDone} 课`;
username.innerHTML += statsHtml;
$.log('更新统计信息:', { total, done, notDone });
},
// 查找并开始未完成课程
async findAndStartCourse() {
const startBtn = await $.waitForElement("button.issue-btn.issue-default-btn.ng-star-inserted");
if (startBtn) startBtn.click();
const progressElements = await $.waitForElements('.progress-num');
const unfinishedProgress = Array.from(progressElements)
.find(el => el.innerText === '0%');
if (unfinishedProgress) {
const courseLink = unfinishedProgress.parentElement.parentElement.querySelector('a');
if (courseLink) {
$.log('开始新课程');
courseLink.click();
return true;
}
}
return false;
}
};
// --- 主程序 ---
(function() {
'use strict';
// 页面加载完成后的初始化
window.addEventListener('load', async () => {
if ($.urlHas('trainPlan')) {
await CourseList.showStats();
} else if ($.urlHas('course')) {
// 页面加载时立即检查课程状态
await VideoControl.updateTitle();
await VideoControl.handlePopup();
await VideoControl.play();
}
}, false);
// 定期检查(针对课程列表)
setInterval(async () => {
try {
if ($.urlHas('trainPlan')) {
await CourseList.findAndStartCourse();
}
} catch (error) {
$.log('执行出错:', error);
}
}, CONFIG.CHECK_INTERVAL_MS);
// 更频繁地检查课程内容(针对视频播放)
setInterval(async () => {
try {
if ($.urlHas('course')) {
await VideoControl.updateTitle();
await VideoControl.checkCompletion();
await VideoControl.handlePopup();
await VideoControl.play();
}
} catch (error) {
$.log('执行出错:', error);
}
}, CONFIG.COURSE_CHECK_INTERVAL_MS);
})();