// ==UserScript==
// @name Sakura安全微伴
// @version 1.0.0
// @description 2026最新安全微伴答题脚本,自动学习+自动考试,支持本地题库和用户上传题库
// @author Sakura
// @match https://*.mycourse.cn/*
// @grant GM_xmlhttpRequest
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_notification
// @grant GM_registerMenuCommand
// @connect cdn.jsdelivr.net
// @connect ghfast.top
// @connect github.com
// @connect weiban.mycourse.cn
// ==/UserScript==
(function () {
'use strict';
// ==================== 配置 ====================
const CONFIG = {
studyTime: 15, // 每门课程学习时间(秒)
examUseTime: 250, // 考试总用时(秒)
answerUrl: 'https://cdn.jsdelivr.net/gh/hangone/WeBan@main/answer/answer.json',
localAnswerKey: 'weban_answers'
};
// ==================== 登录状态管理 ====================
// 从网站获取登录信息
function getLoginInfo() {
const info = {
token: '',
userId: '',
tenantCode: '',
userName: '',
realName: '',
tenantName: ''
};
try {
// 方式1: 从 localStorage 获取
const token = localStorage.getItem('token');
const userStr = localStorage.getItem('user');
if (token) {
info.token = token;
}
if (userStr) {
const user = JSON.parse(userStr);
info.userId = user.userId || '';
info.tenantCode = user.tenantCode || '';
info.userName = user.userName || '';
info.realName = user.realName || '';
info.tenantName = user.tenantName || '';
if (user.token) {
info.token = user.token;
}
}
// 方式2: 从 sessionStorage 获取
if (!info.token) {
const sessionToken = sessionStorage.getItem('token');
if (sessionToken) {
info.token = sessionToken;
}
}
if (!info.userId) {
const sessionUser = sessionStorage.getItem('user');
if (sessionUser) {
const user = JSON.parse(sessionUser);
info.userId = user.userId || '';
info.tenantCode = user.tenantCode || '';
info.userName = user.userName || '';
info.realName = user.realName || '';
info.tenantName = user.tenantName || '';
}
}
// 方式3: 从 URL 参数获取
const urlParams = new URLSearchParams(window.location.search);
if (!info.token) {
info.token = urlParams.get('token') || '';
}
if (!info.tenantCode) {
info.tenantCode = urlParams.get('tenantCode') || '';
}
// 方式4: 从 Cookie 获取
if (!info.token) {
const cookies = document.cookie.split(';');
for (const cookie of cookies) {
const [name, value] = cookie.trim().split('=');
if (name === 'token' || name === 'X-Token') {
info.token = value;
}
if (name === 'tenantCode') {
info.tenantCode = value;
}
}
}
// 方式5: 从 Vue/React 应用状态获取
if (!info.token && window.__INITIAL_STATE__) {
const state = window.__INITIAL_STATE__;
info.token = state.token || state.user?.token || '';
info.userId = state.userId || state.user?.userId || '';
}
// 方式6: 从 Vuex store 获取
if (!info.token && window.__VUE_DEVTOOLS_GLOBAL_HOOK__) {
try {
const stores = Object.values(window.__VUE_DEVTOOLS_GLOBAL_HOOK__.stores || {});
for (const store of stores) {
if (store.token) info.token = store.token;
if (store.userId) info.userId = store.userId;
if (store.user) {
info.token = store.user.token || info.token;
info.userId = store.user.userId || info.userId;
}
}
} catch (e) {}
}
} catch (e) {
console.error('[登录状态] 获取登录信息失败:', e);
}
return info;
}
// 检查是否已登录
function isLoggedIn() {
const info = getLoginInfo();
return !!(info.token && info.userId);
}
// 获取请求头
function getHeaders() {
const info = getLoginInfo();
return {
'Content-Type': 'application/x-www-form-urlencoded',
'X-Token': info.token || '',
'Accept': 'application/json, text/plain, */*',
};
}
// ==================== 工具函数 ====================
// 清理文本(只保留字母、数字和汉字)
function cleanText(text) {
return text.replace(/[^\w\u4e00-\u9fa5]/g, '');
}
// 获取时间戳
function getTimestamp() {
return Date.now().toString();
}
// 延迟函数
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// ==================== 题库管理 ====================
// 加载本地题库
function loadLocalAnswers() {
try {
const data = GM_getValue(CONFIG.localAnswerKey);
return data ? JSON.parse(data) : {};
} catch (e) {
console.error('[题库] 加载本地题库失败:', e);
return {};
}
}
// 保存题库到本地
function saveLocalAnswers(answers) {
try {
GM_setValue(CONFIG.localAnswerKey, JSON.stringify(answers));
console.log('[题库] 题库已保存');
} catch (e) {
console.error('[题库] 保存题库失败:', e);
}
}
// 下载题库
function downloadAnswers() {
return new Promise((resolve, reject) => {
console.log('[题库] 正在下载题库...');
GM_xmlhttpRequest({
method: 'GET',
url: CONFIG.answerUrl,
onload: function(response) {
try {
const answers = JSON.parse(response.responseText);
saveLocalAnswers(answers);
console.log('[题库] 题库下载成功,共', Object.keys(answers).length, '道题目');
resolve(answers);
} catch (e) {
console.error('[题库] 解析题库失败:', e);
reject(e);
}
},
onerror: function(error) {
console.error('[题库] 下载题库失败:', error);
reject(error);
}
});
});
}
// 获取题库(优先本地,否则下载)
async function getAnswers() {
let answers = loadLocalAnswers();
if (Object.keys(answers).length === 0) {
try {
answers = await downloadAnswers();
} catch (e) {
console.error('[题库] 获取题库失败,使用空题库');
}
}
return answers;
}
// 构建答案索引(题目 -> 正确答案列表)
function buildAnswerIndex(answers) {
const index = {};
for (const [title, data] of Object.entries(answers)) {
const cleanTitle = cleanText(title);
if (!index[cleanTitle]) {
index[cleanTitle] = [];
}
const correctOptions = data.optionList
.filter(opt => opt.isCorrect === 1)
.map(opt => cleanText(opt.content));
index[cleanTitle].push(...correctOptions);
}
return index;
}
// 合并题库
function mergeAnswers(oldAnswers, newAnswers) {
const merged = { ...oldAnswers };
for (const [title, data] of Object.entries(newAnswers)) {
if (!merged[title]) {
merged[title] = data;
} else {
// 合并选项
const existingContents = new Set(merged[title].optionList.map(o => o.content));
for (const opt of data.optionList) {
if (!existingContents.has(opt.content)) {
merged[title].optionList.push(opt);
}
}
}
}
return merged;
}
// ==================== API 封装 ====================
// POST 请求
async function postRequest(url, data) {
const info = getLoginInfo();
const formData = new URLSearchParams();
formData.append('tenantCode', info.tenantCode);
formData.append('userId', info.userId);
for (const [key, value] of Object.entries(data)) {
if (value !== undefined && value !== null) {
formData.append(key, value);
}
}
formData.append('timestamp', getTimestamp());
const response = await fetch(url, {
method: 'POST',
headers: getHeaders(),
body: formData.toString()
});
return response.json();
}
// GET 请求
async function getRequest(url, params = {}) {
const info = getLoginInfo();
const searchParams = new URLSearchParams();
searchParams.append('tenantCode', info.tenantCode);
searchParams.append('userId', info.userId);
for (const [key, value] of Object.entries(params)) {
if (value !== undefined && value !== null) {
searchParams.append(key, value);
}
}
searchParams.append('timestamp', getTimestamp());
const response = await fetch(`${url}?${searchParams.toString()}`, {
method: 'GET',
headers: getHeaders()
});
return response.json();
}
// ==================== 学习功能 ====================
// 获取学习任务列表
async function getStudyTasks() {
const result = await postRequest('https://weiban.mycourse.cn/pharos/index/listStudyTask.do', {});
return result.data?.studyTaskList || [];
}
// 获取课程分类
async function getCategories(userProjectId, chooseType) {
const result = await postRequest('https://weiban.mycourse.cn/pharos/usercourse/listCategory.do', {
userProjectId,
chooseType
});
return result.data || [];
}
// 获取课程列表
async function getCourses(userProjectId, categoryCode, chooseType) {
const result = await postRequest('https://weiban.mycourse.cn/pharos/usercourse/listCourse.do', {
userProjectId,
categoryCode,
chooseType
});
return result.data || [];
}
// 开始学习课程
async function studyCourse(courseId, userProjectId) {
return postRequest('https://weiban.mycourse.cn/pharos/usercourse/study.do', {
courseId,
userProjectId
});
}
// 获取课程链接
async function getCourseUrl(courseId, userProjectId) {
const result = await postRequest('https://weiban.mycourse.cn/pharos/usercourse/getCourseUrl.do', {
courseId,
userProjectId
});
return result.data || '';
}
// 完成课程
async function finishCourse(userCourseId, token = null) {
const info = getLoginInfo();
const url = token
? `https://weiban.mycourse.cn/pharos/usercourse/v2/${token}.do`
: `https://weiban.mycourse.cn/pharos/usercourse/v2/${userCourseId}.do`;
const params = new URLSearchParams();
params.append('userCourseId', userCourseId);
params.append('tenantCode', info.tenantCode);
params.append('_', getTimestamp());
const response = await fetch(`${url}?${params.toString()}`, {
method: 'GET',
headers: getHeaders()
});
return response.text();
}
// 自动学习一门课程
async function autoStudyCourse(course, userProjectId) {
console.log(`[学习] 开始学习: ${course.resourceName}`);
// 开始学习
await studyCourse(course.resourceId, userProjectId);
if (!course.userCourseId) {
console.log(`[学习] 课程无需等待: ${course.resourceName}`);
return true;
}
// 获取课程链接
const courseUrl = await getCourseUrl(course.resourceId, userProjectId);
// 检查是否需要验证码
if (courseUrl.includes('csCapt=true')) {
console.warn(`[学习] 课程需要验证码,跳过: ${course.resourceName}`);
return false;
}
// 等待学习时间
console.log(`[学习] 等待 ${CONFIG.studyTime} 秒...`);
await sleep(CONFIG.studyTime * 1000);
// 完成课程
const result = await finishCourse(course.userCourseId);
console.log(`[学习] 完成: ${course.resourceName}`, result);
return true;
}
// 自动学习所有课程
async function autoStudyAll() {
if (!isLoggedIn()) {
alert('请先登录!');
return;
}
console.log('[学习] 开始自动学习...');
log('开始自动学习...');
const tasks = await getStudyTasks();
console.log(`[学习] 获取到 ${tasks.length} 个学习任务`);
log(`获取到 ${tasks.length} 个学习任务`);
for (const task of tasks) {
console.log(`[学习] 处理任务: ${task.projectName}`);
log(`处理任务: ${task.projectName}`);
// 必修课(3)、推送课(1)、自选课(2)
for (const chooseType of [3, 1, 2]) {
const typeNames = { 1: '推送课', 2: '自选课', 3: '必修课' };
const categories = await getCategories(task.userProjectId, chooseType);
for (const category of categories) {
if (category.finishedNum >= category.totalNum) {
console.log(`[学习] ${typeNames[chooseType]}/${category.categoryName} 已完成`);
continue;
}
console.log(`[学习] 处理分类: ${typeNames[chooseType]}/${category.categoryName}`);
log(`处理: ${typeNames[chooseType]}/${category.categoryName}`);
const courses = await getCourses(task.userProjectId, category.categoryCode, chooseType);
for (const course of courses) {
if (course.finished === 1) {
console.log(`[学习] 课程已完成: ${course.resourceName}`);
continue;
}
await autoStudyCourse(course, task.userProjectId);
}
}
}
}
console.log('[学习] 自动学习完成');
log('自动学习完成!');
GM_notification({ title: '学习完成', text: '所有课程学习完成!' });
}
// ==================== 考试功能 ====================
// 获取我的项目列表(用于考试)
async function getMyProjects() {
const result = await postRequest('https://weiban.mycourse.cn/pharos/index/listMyProject.do', { ended: 2 });
return result.data || [];
}
// 获取模块完成情况
async function listCompletion() {
const result = await postRequest('https://weiban.mycourse.cn/pharos/index/listCompletion.do', {});
return result.data || [];
}
// 获取实验室项目
async function getLabProject() {
const result = await postRequest('https://weiban.mycourse.cn/pharos/lab/index.do', {});
return result.data?.current || null;
}
// 获取考试计划
async function getExamPlans(userProjectId) {
const result = await postRequest('https://weiban.mycourse.cn/pharos/exam/listPlan.do', {
userProjectId
});
return result;
}
// 考试前检查
async function examBeforePaper(userExamPlanId) {
return postRequest('https://weiban.mycourse.cn/pharos/exam/beforePaper.do', {
userExamPlanId
});
}
// 准备考试
async function prepareExam(userExamPlanId) {
return postRequest('https://weiban.mycourse.cn/pharos/exam/preparePaper.do', {
userExamPlanId
});
}
// 开始考试(获取题目)
async function startExam(userExamPlanId) {
return postRequest('https://weiban.mycourse.cn/pharos/exam/startPaper.do', {
userExamPlanId
});
}
// 记录答案
async function recordAnswer(userExamPlanId, questionId, useTime, answerIds, examPlanId) {
return postRequest('https://weiban.mycourse.cn/pharos/exam/recordQuestion.do', {
userExamPlanId,
questionId,
useTime,
answerIds: answerIds.join(','),
examPlanId
});
}
// 提交试卷
async function submitExam(userExamPlanId) {
return postRequest('https://weiban.mycourse.cn/pharos/exam/submitPaper.do', {
userExamPlanId
});
}
// 自动考试
async function autoExam() {
if (!isLoggedIn()) {
alert('请先登录!');
return;
}
console.log('[考试] 开始自动考试...');
log('开始自动考试...');
// 加载题库
const answers = await getAnswers();
const answerIndex = buildAnswerIndex(answers);
console.log(`[考试] 题库加载完成,共 ${Object.keys(answerIndex).length} 道题目`);
log(`题库加载完成,共 ${Object.keys(answerIndex).length} 道题目`);
// 获取项目列表
let projects = await getMyProjects();
console.log(`[考试] 获取到 ${projects.length} 个项目`);
// 检查是否有实验室模块
const completion = await listCompletion();
const showableModules = completion.filter(d => d.showable === 1).map(d => d.module);
if (showableModules.includes('labProject')) {
console.log('[考试] 加载实验室课程');
const labProject = await getLabProject();
if (labProject) {
projects.push(labProject);
}
}
for (const project of projects) {
if (!project.userProjectId) continue;
console.log(`[考试] 处理项目: ${project.projectName}`);
log(`处理项目: ${project.projectName}`);
const examPlansResult = await getExamPlans(project.userProjectId);
if (examPlansResult.code !== '0') {
console.error(`[考试] 获取考试计划失败:`, examPlansResult);
continue;
}
const plans = examPlansResult.data || [];
for (const plan of plans) {
console.log(`[考试] 处理考试: ${plan.examPlanName}`);
log(`处理考试: ${plan.examPlanName}`);
const userExamPlanId = plan.id;
const examPlanId = plan.examPlanId;
// 检查是否已考试
if (plan.examFinishNum > 0) {
console.log(`[考试] 已考试 ${plan.examFinishNum} 次,最高成绩 ${plan.examScore} 分`);
log(`已考试 ${plan.examFinishNum} 次,最高 ${plan.examScore} 分`);
// 继续考试,不跳过
}
// 考试前检查
await examBeforePaper(userExamPlanId);
// 准备考试
const prepareInfo = await prepareExam(userExamPlanId);
if (prepareInfo.code !== '0') {
console.error(`[考试] 准备考试失败:`, prepareInfo);
log(`准备考试失败: ${prepareInfo.msg || JSON.stringify(prepareInfo)}`);
continue;
}
const questionNum = prepareInfo.data?.questionNum || 0;
const perTime = Math.max(1, Math.floor(CONFIG.examUseTime / questionNum));
console.log(`[考试] 题目数: ${questionNum}, 每题用时: ${perTime}秒`);
log(`题目数: ${questionNum}, 每题用时: ${perTime}秒`);
// 开始考试
const examResult = await startExam(userExamPlanId);
if (examResult.code !== '0') {
console.error(`[考试] 获取题目失败:`, examResult);
log(`获取题目失败: ${examResult.msg || examResult.detailCode || JSON.stringify(examResult)}`);
if (examResult.detailCode === '10018') {
log('需要手动在网站上开启一次考试后重试');
}
continue;
}
const examData = examResult.data || {};
const questions = examData.questionList || [];
if (questions.length === 0) {
console.error(`[考试] 题目列表为空`);
log('题目列表为空');
continue;
}
let haveAnswer = 0;
let noAnswer = 0;
// 答题
for (let i = 0; i < questions.length; i++) {
const question = questions[i];
const cleanTitle = cleanText(question.title);
const correctAnswers = answerIndex[cleanTitle];
if (correctAnswers && correctAnswers.length > 0) {
haveAnswer++;
// 匹配答案
const answerIds = question.optionList
.filter(opt => correctAnswers.includes(cleanText(opt.content)))
.map(opt => opt.id);
console.log(`[考试] [${i + 1}/${questions.length}] 有答案: ${question.title.substring(0, 30)}...`);
log(`[${i + 1}/${questions.length}] 有答案`);
await sleep(perTime * 1000);
const recordResult = await recordAnswer(userExamPlanId, question.id, perTime + 1, answerIds, examPlanId);
if (recordResult.code !== '0') {
console.error(`[考试] 记录答案失败:`, recordResult);
}
} else {
noAnswer++;
console.warn(`[考试] [${i + 1}/${questions.length}] 无答案: ${question.title.substring(0, 30)}...`);
log(`[${i + 1}/${questions.length}] 无答案(随机选择)`);
// 随机选择一个答案
const randomOption = question.optionList[Math.floor(Math.random() * question.optionList.length)];
await sleep(perTime * 1000);
const recordResult = await recordAnswer(userExamPlanId, question.id, perTime + 1, [randomOption.id], examPlanId);
if (recordResult.code !== '0') {
console.error(`[考试] 记录答案失败:`, recordResult);
}
}
}
console.log(`[考试] 答题完成,有答案: ${haveAnswer}, 无答案: ${noAnswer}`);
log(`答题完成,有答案: ${haveAnswer}, 无答案: ${noAnswer}`);
// 提交试卷
const submitResult = await submitExam(userExamPlanId);
if (submitResult.code === '0') {
const score = submitResult.data?.score;
console.log(`[考试] 提交成功,成绩: ${score}分`);
log(`提交成功,成绩: ${score}分`);
GM_notification({
title: '考试完成',
text: `${project.projectName} - ${plan.examPlanName}\n成绩: ${score}分`
});
} else {
console.error(`[考试] 提交失败:`, submitResult);
log(`提交失败: ${submitResult.msg || JSON.stringify(submitResult)}`);
}
}
}
console.log('[考试] 自动考试完成');
log('自动考试完成!');
}
// ==================== 题库上传功能 ====================
// 创建上传题库的文件输入
function createUploadInput() {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.json';
input.style.display = 'none';
input.id = 'weban-answer-upload';
input.onchange = async (e) => {
const file = e.target.files[0];
if (!file) return;
try {
const text = await file.text();
const newAnswers = JSON.parse(text);
if (typeof newAnswers !== 'object') {
throw new Error('题库格式错误');
}
// 加载现有题库并合并
const oldAnswers = loadLocalAnswers();
const mergedAnswers = mergeAnswers(oldAnswers, newAnswers);
// 保存合并后的题库
saveLocalAnswers(mergedAnswers);
const oldCount = Object.keys(oldAnswers).length;
const newCount = Object.keys(newAnswers).length;
const mergedCount = Object.keys(mergedAnswers).length;
console.log(`[题库] 上传成功,原有 ${oldCount} 题,新增 ${newCount} 题,合并后 ${mergedCount} 题`);
log(`题库上传成功!原有 ${oldCount} 题,新增 ${newCount} 题,合并后 ${mergedCount} 题`);
alert(`题库上传成功!\n原有: ${oldCount} 题\n新增: ${newCount} 题\n合并后: ${mergedCount} 题`);
} catch (err) {
console.error('[题库] 上传失败:', err);
log(`题库上传失败: ${err.message}`);
alert('题库上传失败: ' + err.message);
}
// 清理 input 以便重复上传
input.value = '';
};
document.body.appendChild(input);
return input;
}
// 触发上传
function triggerUpload() {
let input = document.getElementById('weban-answer-upload');
if (!input) {
input = createUploadInput();
}
input.click();
}
// 导出题库
function exportAnswers() {
const answers = loadLocalAnswers();
const count = Object.keys(answers).length;
if (count === 0) {
alert('题库为空,请先下载或上传题库');
return;
}
const blob = new Blob([JSON.stringify(answers, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `weban_answers_${new Date().toISOString().slice(0, 10)}.json`;
a.click();
URL.revokeObjectURL(url);
log(`题库已导出,共 ${count} 题`);
}
// ==================== UI 控制 ====================
// 日志输出
function log(msg) {
console.log('[安全微伴]', msg);
const logEl = document.getElementById('weban-log');
if (logEl) {
logEl.style.display = 'block';
const time = new Date().toLocaleTimeString();
logEl.innerHTML += `<div>[${time}] ${msg}</div>`;
logEl.scrollTop = logEl.scrollHeight;
}
}
// 更新登录状态显示
function updateLoginStatus() {
const info = getLoginInfo();
const statusEl = document.getElementById('weban-login-status');
if (statusEl) {
if (info.token && info.userId) {
statusEl.innerHTML = `<span style="color: #52c41a;">✓ 已登录</span><br><small>${info.realName || info.userName || ''} (${info.tenantName || ''})</small>`;
} else {
statusEl.innerHTML = '<span style="color: #ff4d4f;">✗ 未登录</span>';
}
}
}
// 创建控制面板
function createControlPanel() {
const panel = document.createElement('div');
panel.id = 'weban-control-panel';
panel.innerHTML = `
<style>
#weban-control-panel {
position: fixed;
top: 10px;
right: 10px;
z-index: 99999;
background: #fff;
border: 1px solid #ddd;
border-radius: 8px;
padding: 15px;
box-shadow: 0 2px 12px rgba(0,0,0,0.15);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
min-width: 220px;
}
#weban-control-panel h3 {
margin: 0 0 10px 0;
font-size: 14px;
color: #333;
border-bottom: 1px solid #eee;
padding-bottom: 8px;
}
#weban-control-panel .status {
background: #f5f5f5;
padding: 8px;
border-radius: 4px;
margin-bottom: 10px;
font-size: 12px;
}
#weban-control-panel button {
display: block;
width: 100%;
margin: 5px 0;
padding: 8px 12px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 13px;
transition: all 0.2s;
}
#weban-control-panel .btn-study {
background: #1890ff;
color: white;
}
#weban-control-panel .btn-exam {
background: #52c41a;
color: white;
}
#weban-control-panel .btn-download {
background: #faad14;
color: white;
}
#weban-control-panel .btn-upload {
background: #722ed1;
color: white;
}
#weban-control-panel .btn-export {
background: #13c2c2;
color: white;
}
#weban-control-panel .btn-close {
background: #f5f5f5;
color: #666;
}
#weban-control-panel button:hover {
opacity: 0.85;
transform: translateY(-1px);
}
#weban-control-panel button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
#weban-control-panel .log {
margin-top: 10px;
max-height: 150px;
overflow-y: auto;
font-size: 11px;
color: #666;
background: #f9f9f9;
padding: 8px;
border-radius: 4px;
display: none;
}
#weban-control-panel .btn-group {
display: flex;
gap: 5px;
}
#weban-control-panel .btn-group button {
flex: 1;
}
</style>
<h3>🎓 Sakura安全微伴助手</h3>
<div class="status" id="weban-login-status">检测登录状态中...</div>
<button class="btn-study" id="btn-auto-study">📚 自动学习</button>
<button class="btn-exam" id="btn-auto-exam">📝 自动考试</button>
<div class="btn-group">
<button class="btn-download" id="btn-download-answers">⬇️ 下载题库</button>
<button class="btn-upload" id="btn-upload-answers">⬆️ 上传题库</button>
</div>
<button class="btn-export" id="btn-export-answers">📤 导出题库</button>
<button class="btn-close" id="btn-close-panel">✕ 关闭</button>
<div class="log" id="weban-log"></div>
`;
document.body.appendChild(panel);
// 绑定事件
document.getElementById('btn-auto-study').onclick = async () => {
const btn = document.getElementById('btn-auto-study');
btn.disabled = true;
btn.textContent = '📚 学习中...';
await autoStudyAll();
btn.disabled = false;
btn.textContent = '📚 自动学习';
};
document.getElementById('btn-auto-exam').onclick = async () => {
const btn = document.getElementById('btn-auto-exam');
btn.disabled = true;
btn.textContent = '📝 考试中...';
await autoExam();
btn.disabled = false;
btn.textContent = '📝 自动考试';
};
document.getElementById('btn-download-answers').onclick = async () => {
log('正在下载题库...');
try {
await downloadAnswers();
log('题库下载成功!');
} catch (e) {
log('题库下载失败: ' + e.message);
}
};
document.getElementById('btn-upload-answers').onclick = () => {
triggerUpload();
};
document.getElementById('btn-export-answers').onclick = () => {
exportAnswers();
};
document.getElementById('btn-close-panel').onclick = () => {
panel.style.display = 'none';
};
// 更新登录状态
updateLoginStatus();
// 定时更新登录状态
setInterval(updateLoginStatus, 5000);
}
// 注册油猴菜单命令
function registerMenuCommands() {
GM_registerMenuCommand('📚 自动学习', autoStudyAll);
GM_registerMenuCommand('📝 自动考试', autoExam);
GM_registerMenuCommand('⬇️ 下载题库', async () => {
try {
await downloadAnswers();
alert('题库下载成功!');
} catch (e) {
alert('题库下载失败: ' + e.message);
}
});
GM_registerMenuCommand('⬆️ 上传题库', triggerUpload);
GM_registerMenuCommand('📤 导出题库', exportAnswers);
}
// ==================== 课程页面自动完成 ====================
// 在课程页面自动完成
function handleCoursePage() {
if (location.href.startsWith('https://mcwk.mycourse.cn')) {
console.log('[自动完成课程] 当前页面符合要求,等待 13 秒...');
setTimeout(() => {
if (typeof finishWxCourse === 'function') {
console.log('[自动完成课程] 执行 finishWxCourse()');
finishWxCourse();
} else {
console.warn('[自动完成课程] 未找到 finishWxCourse 函数');
}
}, 13000);
}
}
// 点击返回按钮
function clickReturnButton() {
let attempts = 0;
const max = 10;
const timer = setInterval(() => {
attempts++;
const btns = document.querySelectorAll('.comment-footer-button');
for (const btn of btns) {
if (btn.textContent.includes('返回列表')) {
console.log('[自动课程] 点击"返回列表"按钮');
btn.click();
clearInterval(timer);
return;
}
}
if (attempts >= max) {
console.warn('[自动课程] 未找到返回按钮');
clearInterval(timer);
}
}, 500);
}
// ==================== URL 变化监听 ====================
function onUrlChange(callback) {
let oldHref = location.href;
const observer = new MutationObserver(() => {
if (location.href !== oldHref) {
oldHref = location.href;
callback(location.href);
}
});
observer.observe(document.body, { childList: true, subtree: true });
}
// ==================== 主逻辑 ====================
function runLogic(url) {
// 课程页面自动完成
handleCoursePage();
// 评论页返回
if (url.startsWith('https://weiban.mycourse.cn/#/wk/comment')) {
console.log('[自动课程] 进入评论页,尝试点击"返回列表"按钮...');
clickReturnButton();
}
}
// 初始化
function init() {
// 注册油猴菜单
registerMenuCommands();
// 只在 weiban.mycourse.cn 主站显示控制面板
if (location.hostname === 'weiban.mycourse.cn') {
// 等待页面加载完成
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', createControlPanel);
} else {
createControlPanel();
}
}
// 初次执行
runLogic(location.href);
// 监听路由变化
onUrlChange((newUrl) => {
console.log('[自动课程] 路由变化:', newUrl);
runLogic(newUrl);
});
}
init();
})();