// ==UserScript== // @name 超星学习通作业题目提取工具 // @namespace http://tampermonkey.net/ // @version 1.2 // @description 提取超星学习通作业页面的题目、选项和答案,支持Word下载 // @author Assistant // @match *://*.chaoxing.com/mooc-ans/mooc2/work/view* // @grant none // @require https://unpkg.com/docx@7.8.2/build/index.js // @require https://unpkg.com/file-saver@2.0.5/dist/FileSaver.min.js // ==/UserScript== (function() { 'use strict'; // 等待页面加载完成 function waitForElement(selector, timeout = 10000) { return new Promise((resolve, reject) => { const startTime = Date.now(); function check() { const element = document.querySelector(selector); if (element) { resolve(element); } else if (Date.now() - startTime > timeout) { reject(new Error(`元素 ${selector} 未找到`)); } else { setTimeout(check, 100); } } check(); }); } // 题目数据结构 class Question { constructor() { this.type = ''; // 题目类型 this.number = ''; // 题目编号 this.content = ''; // 题目内容 this.options = []; // 选项列表 this.myAnswer = ''; // 我的答案 this.correctAnswer = ''; // 正确答案 this.score = ''; // 得分 this.isCorrect = false; // 是否正确 } } // 题目解析器 class QuestionParser { constructor() { this.questions = []; } // 解析所有题目 parseAllQuestions() { this.questions = []; // 获取所有题目容器 const questionContainers = document.querySelectorAll('.questionLi'); questionContainers.forEach((container, index) => { try { const question = this.parseQuestion(container, index + 1); if (question) { this.questions.push(question); } } catch (error) { console.error(`解析第${index + 1}题时出错:`, error); } }); return this.questions; } // 解析单个题目 parseQuestion(container, index) { const question = new Question(); // 解析题目编号和类型 const titleElement = container.querySelector('.mark_name'); if (titleElement) { const titleText = titleElement.textContent.trim(); const typeMatch = titleText.match(/\((.*?题)\)/); const numberMatch = titleText.match(/^(\d+)\./); question.type = typeMatch ? typeMatch[1] : '未知题型'; question.number = numberMatch ? numberMatch[1] : index.toString(); } // 解析题目内容 const contentElement = container.querySelector('.qtContent'); if (contentElement) { question.content = this.cleanText(contentElement.textContent); } // 解析选项(仅适用于选择题和判断题) const optionElements = container.querySelectorAll('.mark_letter li, .qtDetail li'); optionElements.forEach(option => { const optionText = this.cleanText(option.textContent); if (optionText) { question.options.push(optionText); } }); // 解析我的答案 const myAnswerElements = container.querySelectorAll('.stuAnswerContent'); const myAnswers = []; myAnswerElements.forEach(elem => { const answerText = this.cleanText(elem.textContent); if (answerText) { myAnswers.push(answerText); } }); question.myAnswer = myAnswers.join('; '); // 解析正确答案 const correctAnswerElements = container.querySelectorAll('.rightAnswerContent'); const correctAnswers = []; correctAnswerElements.forEach(elem => { const answerText = this.cleanText(elem.textContent); if (answerText) { correctAnswers.push(answerText); } }); question.correctAnswer = correctAnswers.join('; '); // 解析得分 const scoreElement = container.querySelector('.totalScore i'); if (scoreElement) { question.score = scoreElement.textContent.trim(); } // 判断是否正确 const correctIcon = container.querySelector('.marking_dui'); question.isCorrect = !!correctIcon; return question; } // 清理文本内容 cleanText(text) { if (!text) return ''; return text.replace(/\s+/g, ' ').trim(); } // 获取作业标题 getWorkTitle() { const titleElement = document.querySelector('.mark_title'); return titleElement ? this.cleanText(titleElement.textContent) : '作业题目'; } // 获取统计信息 getStatistics() { const stats = { totalQuestions: this.questions.length, correctCount: this.questions.filter(q => q.isCorrect).length, totalScore: '0', maxScore: '0' }; // 获取总分 const scoreElement = document.querySelector('.resultNum i'); if (scoreElement) { stats.totalScore = scoreElement.textContent.trim(); } // 获取满分 const maxScoreElement = document.querySelector('.infoHead span:nth-child(2)'); if (maxScoreElement) { const match = maxScoreElement.textContent.match(/满分:\s*(\d+)/); if (match) { stats.maxScore = match[1]; } } stats.accuracy = stats.totalQuestions > 0 ? ((stats.correctCount / stats.totalQuestions) * 100).toFixed(1) + '%' : '0%'; return stats; } } // 检查必要的库是否加载 function checkLibraries() { const errors = []; if (typeof window.docx === 'undefined') { errors.push('docx库未加载'); } if (typeof window.saveAs === 'undefined' && typeof saveAs === 'undefined') { errors.push('FileSaver库未加载'); } return errors; } // Word文档生成器 class WordGenerator { constructor() { this.docx = window.docx; this.saveAs = window.saveAs || saveAs; } // 生成Word文档 async generateWord(questions, title, stats, options = {}) { const { includeMyAnswer = true, includeCorrectAnswer = true, includeScore = true, includeStatistics = true } = options; const doc = new this.docx.Document({ sections: [{ properties: {}, children: [ // 标题 new this.docx.Paragraph({ children: [ new this.docx.TextRun({ text: title, bold: true, size: 32 }) ], alignment: this.docx.AlignmentType.CENTER, spacing: { after: 400 } }), // 统计信息 ...(includeStatistics ? this.createStatisticsSection(stats) : []), // 题目列表 ...this.createQuestionsSection(questions, includeMyAnswer, includeCorrectAnswer, includeScore) ] }] }); return doc; } // 创建统计信息部分 createStatisticsSection(stats) { return [ new this.docx.Paragraph({ children: [ new this.docx.TextRun({ text: "答题统计", bold: true, size: 24 }) ], spacing: { before: 200, after: 200 } }), new this.docx.Paragraph({ children: [ new this.docx.TextRun({ text: `总题数: ${stats.totalQuestions}题 | 正确: ${stats.correctCount}题 | 准确率: ${stats.accuracy} | 得分: ${stats.totalScore}/${stats.maxScore}分` }) ], spacing: { after: 400 } }) ]; } // 创建题目部分 createQuestionsSection(questions, includeMyAnswer, includeCorrectAnswer, includeScore) { const elements = []; questions.forEach((question, index) => { // 题目标题 elements.push( new this.docx.Paragraph({ children: [ new this.docx.TextRun({ text: `${question.number}. [${question.type}] `, bold: true }), new this.docx.TextRun({ text: question.content }) ], spacing: { before: 300, after: 200 } }) ); // 选项 if (question.options.length > 0) { question.options.forEach(option => { elements.push( new this.docx.Paragraph({ children: [ new this.docx.TextRun({ text: option }) ], indent: { left: 400 }, spacing: { after: 100 } }) ); }); } // 我的答案 if (includeMyAnswer && question.myAnswer) { elements.push( new this.docx.Paragraph({ children: [ new this.docx.TextRun({ text: "我的答案: ", bold: true, color: "0066CC" }), new this.docx.TextRun({ text: question.myAnswer, color: "0066CC" }) ], spacing: { after: 100 } }) ); } // 正确答案 if (includeCorrectAnswer && question.correctAnswer) { elements.push( new this.docx.Paragraph({ children: [ new this.docx.TextRun({ text: "正确答案: ", bold: true, color: "009900" }), new this.docx.TextRun({ text: question.correctAnswer, color: "009900" }) ], spacing: { after: 100 } }) ); } // 得分和正确性 if (includeScore && question.score) { elements.push( new this.docx.Paragraph({ children: [ new this.docx.TextRun({ text: `得分: ${question.score}分 `, bold: true }), new this.docx.TextRun({ text: question.isCorrect ? "✓ 正确" : "✗ 错误", color: question.isCorrect ? "009900" : "CC0000", bold: true }) ], spacing: { after: 200 } }) ); } // 分隔线 // if (index < questions.length - 1) { // elements.push( // new this.docx.Paragraph({ // children: [ // new this.docx.TextRun({ // text: "─".repeat(50), // color: "CCCCCC" // }) // ], // alignment: this.docx.AlignmentType.CENTER, // spacing: { before: 200, after: 200 } // }) // ); // } }); return elements; } // 下载JSON数据 downloadJSON() { if (this.questions.length === 0) { this.showMessage('请先解析题目', 'error'); return; } try { const title = this.parser.getWorkTitle(); const stats = this.parser.getStatistics(); // 获取导出选项 const options = { includeMyAnswer: document.getElementById('include-my-answer').checked, includeCorrectAnswer: document.getElementById('include-correct-answer').checked, includeScore: document.getElementById('include-score').checked, includeStatistics: document.getElementById('include-statistics').checked }; // 根据选项过滤题目数据 const filteredQuestions = this.questions.map(question => { const filtered = { type: question.type, number: question.number, content: question.content, options: question.options, isCorrect: question.isCorrect }; if (options.includeMyAnswer) { filtered.myAnswer = question.myAnswer; } if (options.includeCorrectAnswer) { filtered.correctAnswer = question.correctAnswer; } if (options.includeScore) { filtered.score = question.score; } return filtered; }); const data = { title: title, exportTime: new Date().toLocaleString(), exportOptions: options, statistics: options.includeStatistics ? stats : null, questions: filteredQuestions }; const jsonStr = JSON.stringify(data, null, 2); const blob = new Blob([jsonStr], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${title}_${new Date().toLocaleDateString().replace(/\//g, '-')}.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); this.showMessage('JSON数据下载成功!', 'success'); } catch (error) { console.error('下载JSON时出错:', error); this.showMessage(`下载失败: ${error.message}`, 'error'); } } async downloadWord(doc, filename) { try { const blob = await this.docx.Packer.toBlob(doc); // 尝试多种下载方式 if (this.saveAs) { this.saveAs(blob, `${filename}.docx`); } else if (typeof saveAs !== 'undefined') { saveAs(blob, `${filename}.docx`); } else { // 备用下载方法 const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${filename}.docx`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } return true; } catch (error) { console.error('下载Word文档时出错:', error); return false; } } } // UI界面 class ExtractorUI { constructor() { this.parser = new QuestionParser(); this.wordGenerator = new WordGenerator(); this.isVisible = false; this.questions = []; } // 创建UI界面 createUI() { const container = document.createElement('div'); container.id = 'chaoxing-extractor'; container.innerHTML = `

题目提取工具

导出选项
`; document.body.appendChild(container); return container; } // 创建浮动按钮 createFloatButton() { const button = document.createElement('button'); button.id = 'chaoxing-float-btn'; button.innerHTML = '📝'; button.title = '题目提取工具'; button.style.cssText = ` position: fixed; bottom: 80px; right: 20px; width: 56px; height: 56px; border-radius: 50%; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; font-size: 24px; cursor: pointer; box-shadow: 0 4px 20px rgba(102, 126, 234, 0.4); z-index: 9999; transition: all 0.3s ease; `; button.addEventListener('mouseenter', () => { button.style.transform = 'scale(1.1)'; button.style.boxShadow = '0 6px 25px rgba(102, 126, 234, 0.6)'; }); button.addEventListener('mouseleave', () => { button.style.transform = 'scale(1)'; button.style.boxShadow = '0 4px 20px rgba(102, 126, 234, 0.4)'; }); button.onclick = () => this.toggle(); document.body.appendChild(button); } // 显示界面 show() { this.isVisible = true; document.getElementById('chaoxing-extractor').classList.add('visible'); } // 隐藏界面 hide() { this.isVisible = false; document.getElementById('chaoxing-extractor').classList.remove('visible'); } // 切换显示状态 toggle() { if (this.isVisible) { this.hide(); } else { this.show(); } } // 显示消息 showMessage(text, type = 'success') { const container = document.getElementById('message-container'); container.innerHTML = `
${text}
`; setTimeout(() => { container.innerHTML = ''; }, 3000); } // 更新统计信息 updateStats(stats) { const container = document.getElementById('stats-container'); container.innerHTML = `
答题统计
题目总数: ${stats.totalQuestions}题
正确题数: ${stats.correctCount}题
正确率: ${stats.accuracy}
得分: ${stats.totalScore}/${stats.maxScore}分
`; } // 解析题目 async parseQuestions() { const parseBtn = document.getElementById('parse-btn'); const downloadBtn = document.getElementById('download-btn'); const downloadJsonBtn = document.getElementById('download-json-btn'); // 更新按钮状态 parseBtn.innerHTML = '
解析中...'; parseBtn.disabled = true; downloadBtn.disabled = true; downloadJsonBtn.disabled = true; try { // 解析题目 this.questions = this.parser.parseAllQuestions(); if (this.questions.length === 0) { throw new Error('未找到任何题目,请确保页面已完全加载'); } // 获取统计信息 const stats = this.parser.getStatistics(); this.updateStats(stats); // 显示成功消息 this.showMessage(`成功解析 ${this.questions.length} 道题目`, 'success'); // 启用下载按钮 downloadBtn.disabled = false; downloadJsonBtn.disabled = false; } catch (error) { console.error('解析题目时出错:', error); this.showMessage(`解析失败: ${error.message}`, 'error'); } finally { // 恢复按钮状态 parseBtn.innerHTML = '📋 解析题目'; parseBtn.disabled = false; } } // 下载Word文档 async downloadWord() { if (this.questions.length === 0) { this.showMessage('请先解析题目', 'error'); return; } // 检查库是否可用 if (!this.wordGenerator.docx) { this.showMessage('docx库未加载,无法生成Word文档', 'error'); return; } const downloadBtn = document.getElementById('download-btn'); downloadBtn.innerHTML = '
生成中...'; downloadBtn.disabled = true; try { // 获取导出选项 const options = { includeMyAnswer: document.getElementById('include-my-answer').checked, includeCorrectAnswer: document.getElementById('include-correct-answer').checked, includeScore: document.getElementById('include-score').checked, includeStatistics: document.getElementById('include-statistics').checked }; // 获取标题和统计信息 const title = this.parser.getWorkTitle(); const stats = this.parser.getStatistics(); // 生成Word文档 console.log('开始生成Word文档...'); const doc = await this.wordGenerator.generateWord(this.questions, title, stats, options); // 下载文档 const filename = `${title}_${new Date().toLocaleDateString().replace(/\//g, '-')}`; console.log('开始下载Word文档...'); const success = await this.wordGenerator.downloadWord(doc, filename); if (success) { this.showMessage('Word文档下载成功!', 'success'); } else { throw new Error('下载过程中发生错误'); } } catch (error) { console.error('下载Word文档时出错:', error); this.showMessage(`下载失败: ${error.message}`, 'error'); } finally { downloadBtn.innerHTML = '📄 下载Word文档'; downloadBtn.disabled = false; } } // 下载JSON数据 downloadJSON() { if (this.questions.length === 0) { this.showMessage('请先解析题目', 'error'); return; } try { const title = this.parser.getWorkTitle(); const stats = this.parser.getStatistics(); const data = { title: title, exportTime: new Date().toLocaleString(), statistics: stats, questions: this.questions }; const jsonStr = JSON.stringify(data, null, 2); const blob = new Blob([jsonStr], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${title}_${new Date().toLocaleDateString().replace(/\//g, '-')}.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); this.showMessage('JSON数据下载成功!', 'success'); } catch (error) { console.error('下载JSON时出错:', error); this.showMessage(`下载失败: ${error.message}`, 'error'); } } // 初始化 async init() { // 检查必要的库 const libErrors = checkLibraries(); if (libErrors.length > 0) { console.error('库加载检查失败:', libErrors); alert(`题目提取工具初始化失败:\n${libErrors.join('\n')}\n\n请检查网络连接或稍后重试`); return; } // 等待页面加载 try { await waitForElement('.questionLi', 10000); // 创建UI this.createUI(); this.createFloatButton(); // 设置全局引用 window.extractorUI = this; console.log('超星学习通题目提取工具已加载'); } catch (error) { console.error('初始化失败:', error); alert(`题目提取工具初始化失败:${error.message}`); } } } // 启动应用 function startApp() { // 等待库加载完成 const checkInterval = setInterval(() => { const errors = checkLibraries(); if (errors.length === 0) { clearInterval(checkInterval); const extractorUI = new ExtractorUI(); extractorUI.init(); } }, 500); // 超时处理 setTimeout(() => { clearInterval(checkInterval); const errors = checkLibraries(); if (errors.length > 0) { console.warn('库加载超时,尝试强制启动...'); const extractorUI = new ExtractorUI(); extractorUI.init(); } }, 10000); } // 页面加载完成后启动 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', startApp); } else { startApp(); } })();