在您安裝前,Greasy Fork希望您了解本腳本包含“可能不受歡迎的功能”,可能幫助腳本的作者獲利,而不能給你帶來任何收益。
此腳本會在您造訪的網站插入廣告。
华师网络教育学习助手,自动学习、获取题库
// ==UserScript== // @name 华师网络教育学习助手 // @version 0.0.5 // @namespace http://tampermonkey.net/ // @description 华师网络教育学习助手,自动学习、获取题库 // @author 4Ark // @match *https://gdou.scnu.edu.cn/learnspace/learn/learn/blue/index.action* // @match *https://scnu-exam.webtrn.cn/platformwebapi/student/exam/studentExam_queryExamInfo.action* // @match *https://gdou.scnu.edu.cn/learnspace/learn/learn/blue/exam_checkPaperToexam.action* // @match *https://scnu-exam.webtrn.cn/student/exam/studentExam_studentInfo.action* // @match *https://scnu-exam.webtrn.cn/exam/examflow_index.action* // @run-at document-end // @grant unsafeWindow // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_info // @grant GM_setClipboard // @antifeature ads // @license MIT // ==/UserScript== ;(function () { 'use strict' const DEFAULT_AUTO_GET_EXAM_COUNT = 15 const PLAY_SPEED_STRATEGY = { 25: 16, 15: 12, 10: 8, 3: 4, 2: 2, 1: 1 } const EXAM_IDS_KEY = 'huashi-exam-ids' const QUESTION_TYPE_SORT = { 单项选择题: 1, 多项选择题: 2, 判断题: 3, 填空题: 4 } const UTILS_TYPE = { LEARN: '自动学习', GET_EXAM: '获取题库' } let util_type = UTILS_TYPE.LEARN const CHAPTER_TYPE = { VIDEO: 'video', TEXT: 'text' } const GET_EXAM_BASE_URL = 'https://scnu-exam.webtrn.cn/platformwebapi/student/exam/studentExam_queryExamInfo.action' const GET_EXAM_BASE_URL_2 = 'https://gdou.scnu.edu.cn/learnspace/learn/learn/blue/exam_checkPaperToexam.action' const SURE_EXAM_BASE_URL = 'https://scnu-exam.webtrn.cn/student/exam/studentExam_studentInfo.action' const EXAM_BASE_URL = 'https://scnu-exam.webtrn.cn/exam/examflow_index.action' let currentPage let currentFrame const delay = (time) => new Promise((resolve) => { setTimeout(() => resolve(), time) }) function main() { const getType = function () { const module = getCurrentModule() if (module.includes('教学视频')) { return UTILS_TYPE.LEARN } if (module.includes('在线练习')) { return UTILS_TYPE.GET_EXAM } } $('.nav .nav_li').click(function () { var tab = $(this).find('a').text() if (tab === '课程内容') { initToolbar() awaitPageLoaded(function () { setMessage('准备中...') awaitFrameLoaded(() => { const type = getType() const actions = { [UTILS_TYPE.LEARN]: startLearn, [UTILS_TYPE.GET_EXAM]: getExam } setUtilType(type) actions[type] && actions[type]() }) }) return } $('#huashi-exam-toolbar').remove() }) if (location.href.includes(GET_EXAM_BASE_URL)) { openExam() } if (location.href.includes(GET_EXAM_BASE_URL_2)) { getExam() } if (location.href.includes(SURE_EXAM_BASE_URL)) { sureExam() } if (location.href.includes(EXAM_BASE_URL)) { submitExam() } } function initToolbar() { if ($('#huashi-exam-toolbar').length) return const messageBox = $( `<div id="huashi-exam-toolbar"> <p style="color:red">华师网络教育学习助手</p> <div id="huashi-exam-message"></div> <div id="huashi-exam-util-type">当前功能:<span>${util_type}</span></div> </div>` ) const css = ` #huashi-exam-toolbar { position: absolute; width: 100%; height: 50px; padding: 0px 20px; background: rgb(255, 255, 255); top: 0px; left: 0px; color: red; display: flex; justify-content: space-between; align-items: center; box-sizing: border-box; } #huashi-exam-message { color: red; } ` $('body').append(messageBox) $('head').append($('<style type="text/css">').html(css)) } function getCurrentModule() { return $page('#nav .vtitle span.v2').text() } function $page(selector, context = currentPage) { return $(context).contents().find(selector) } function setMessage(message) { $('#huashi-exam-message').text(message) } function setUtilType(type) { util_type = type $('#huashi-exam-util-type span').text(util_type) setMessage('') } function awaitPageLoaded(cb) { const page = $('#mainContent') $(page).one('load', function () { currentPage = $(this) cb() }) } function awaitFrameLoaded(cb) { $(currentPage) .contents() .find('#mainFrame') .on('load', function () { currentFrame = $(this) cb() }) } function fmtMSS(s) { s = parseInt(s) if (s < 0 || isNaN(s)) return '' return (s - (s %= 60)) / 60 + (s > 9 ? ':' : ':0') + s } function fmtM(s) { s = parseInt(s) return (s - (s %= 60)) / 60 } function startLearn() { const getSpeed = function (minute) { const key = Object.keys(PLAY_SPEED_STRATEGY) .sort((a, b) => b - a) .find(function (m) { return m <= minute }) return PLAY_SPEED_STRATEGY[key] || 1 } const passVideo = function (video, next) { const duration = fmtMSS(video.duration) const minute = fmtM(video.duration) const speed = getSpeed(minute) setMessage( '当前视频时长:' + duration + ',将采用' + speed + '倍速播放,预计需要' + (minute * (speed / 100)).toFixed(1) + '分钟。' ) video.muted = true video.playbackRate = speed const timer = setInterval(function () { if (video && video.playbackRate !== speed) { video.playbackRate = speed } }, 5000) $(video).on('ended', function () { clearInterval(timer) next() }) } const passChapterByType = function (type, context) { const next = getNext() if (type === CHAPTER_TYPE.TEXT) { return next() } if (type === CHAPTER_TYPE.VIDEO) { return passVideo(context, next) } } const getNext = function () { const menus = $page('.menuct .menub') const menu = $page('.menuct .menubu') const index = menus.index(menu) const nextChapter = function () { const chapters = $page('.vcon:visible .vconlist > li') const chapter = $page('.vcon:visible .vconlist > li.select') const index = chapters.index(chapter) if (index === chapters.length - 1) { return () => { setMessage('学习完毕') } } return function () { setMessage('正在加载...') setTimeout(() => { $(chapters[index + 1]).click() $(chapters[index + 1]) .find('a') .click() }, 500) } } if (index === menus.length - 1) { return nextChapter() } return function () { setMessage('正在加载...') $page('#rtarr').click() } } const awaitVideoLoaded = function (cb) { var target = $(currentFrame).contents().find('body')[0] const textContent = $(target).find('#textContent') if (textContent.length >= 1) { cb(CHAPTER_TYPE.TEXT, textContent) return } let loadedVideo var observer = new MutationObserver(function (mutations) { mutations.forEach(function () { if (loadedVideo) return const video = $(target).find('.cont video') if (video.attr('src')) { loadedVideo = true video.on('loadedmetadata', function () { cb(CHAPTER_TYPE.VIDEO, video[0]) }) observer.disconnect() } }) }) observer.observe(target, { subtree: true, childList: true }) } awaitVideoLoaded((type, context) => { setTimeout(() => { passChapterByType(type, context) }, 500) }) } function getExam() { setUtilType(UTILS_TYPE.GET_EXAM) const isSolo = location.href.includes(GET_EXAM_BASE_URL_2) if (!isSolo) { const isConfirm = window.confirm('是否需要自动获取题库?') if (!isConfirm) return } const autoGetExamCount = window.prompt( '请输入自动获取题库次数:', DEFAULT_AUTO_GET_EXAM_COUNT ) GM_setValue('autoGetExamCount', autoGetExamCount) GM_setValue(EXAM_IDS_KEY, []) if (isSolo) { window.open($('#examIframe', currentFrame).attr('src')) } else { window.open($page('#examIframe', currentFrame).attr('src')) } } async function openExam() { if (self != top) { return } const ids = GM_getValue(EXAM_IDS_KEY) || [] console.log('4ark ids -->', ids) const count = GM_getValue('autoGetExamCount') || DEFAULT_AUTO_GET_EXAM_COUNT console.log('4ark count -->', count) if (ids.length >= count) { initToolbar() setUtilType(UTILS_TYPE.GET_EXAM) setMessage('正在下载题库...') return downloadExam(ids) } await delay(500) console.log(`4ark $('#viewRecordBtn').length`, $('#viewRecordBtn').length) if ($('#viewRecordBtn').length) { GM_setValue(EXAM_IDS_KEY, [ ...ids, $('#viewRecordBtn').attr('href').split('=').pop() ]) } ;`` await delay(500) $('#goBtn').click() await delay(500) $('.TB_command_btn ').eq(1).click() } async function sureExam() { const isExercise = $('.exam-primTit').text().includes('在线练习') if (!isExercise) return await delay(500) console.log('4ark', $('.submit_solid')) $('.submit_solid').click() await delay(500) $('.TB_command_btn ').eq(1).click() } async function submitExam() { const isExercise = $('.paper_name').text().includes('在线练习') if (!isExercise) return await delay(500) $('.paper_submit').click() await delay(500) $('.win_btn1').eq(1).click() await delay(500) $('.TB_command_btn').click() } async function downloadExam(ids) { const tasks = ids.map((id) => requestExam(id)) const result = await Promise.all(tasks) const html = getHTML(result) const frag = document.createDocumentFragment() const div = $(`<div id="huashi-exam-download-div">${html}</div>`) div.css({ display: 'none' }) frag.appendChild(div[0]) $('body').append(frag) const download = function (content, filename) { var eleLink = document.createElement('a') eleLink.download = filename eleLink.style.display = 'none' // 字符内容转变成blob地址 var blob = new Blob([content]) eleLink.href = URL.createObjectURL(blob) // 触发点击 document.body.appendChild(eleLink) eleLink.click() // 然后移除 document.body.removeChild(eleLink) } const downloadHTML = function (questions) { const html = questions .map((question, index) => { return `<body style="padding: 32px;"><div class="question" style="margin-bottom: 20px;"> <div class="question-content" style="display: flex;">${ index + 1 }、${ question.content?.src ? `<img src="${question.content.src}" style="margin-bottom: 20px;" />` : question.content }</div> <div class="question-options" style="padding-left: 20px;">${question.options .map((option) => `<li>${option}</li>`) .join('')}</div> <div class="question-answer" style="padding-left: 20px; margin-top: 10px;">参考答案:${ question.answer?.src ? `<img src="${question.answer.src}" style="margin-bottom: 20px;" />` : question.answer }</div> </div></body>` }) .join('') download(html, $('.mod_tit h2').text().trim() + '题库.html') GM_setValue(EXAM_IDS_KEY, []) setMessage('下载完成') } const getQuestions = function () { let questions = $('.q_content') .map((_, el) => { let content = $(el) .find('.divQuestionTitle') .text() .replace(/\d+、/, '') const img = $(el).find('.divQuestionTitle img').attr('src') if (img) { content = { src: img.startsWith('https') ? img : `https://scnu-exam.webtrn.cn${img}` } } const options = $(el) .find('.q_option_readonly') .map(function () { return $(this).text() }) .get() let answer = $(el).find('.exam_rightAnswer span[name=rightAnswer]').text() || $(el).find('.exam_rightAnswer .has_standard_answer').text() const answerImg = $(el) .find('.exam_rightAnswer .has_standard_answer img') .attr('src') if (answerImg) { answer = { src: answerImg.startsWith('https') ? answerImg : `https://scnu-exam.webtrn.cn${answerImg}` } } return { content, options, answer } }) .get() const arrayUniqueByKey = (array, key) => { return [...new Map(array.map((item) => [item[key], item])).values()] } questions = arrayUniqueByKey(questions, 'content') downloadHTML(questions) } getQuestions() } function requestExam(id) { return new Promise((resolve) => { $.ajax({ url: 'https://scnu-exam.webtrn.cn/student/exam/examrecord_getRecordPaperStructure.action', type: 'POST', headers: { Authority: 'scnu-exam.webtrn.cn', Pragma: 'no-cache', 'Cache-Control': 'no-cache', Accept: '*/*', 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'X-Requested-With': 'XMLHttpRequest', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36 Edg/93.0.961.47', Origin: 'https://scnu-exam.webtrn.cn', 'Sec-Fetch-Site': 'same-origin', 'Sec-Fetch-Mode': 'cors', 'Sec-Fetch-Dest': 'empty', Referer: `https://scnu-exam.webtrn.cn/student/exam/examrecord_recordDetail.action?recordId=${id}`, 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', Cookie: document.cookie, 'Accept-Encoding': 'gzip' }, contentType: 'application/x-www-form-urlencoded', data: { recordId: id } }).done(function (data) { data = JSON.parse(data) const { contentList, examBatchId } = data.data const contentIds = contentList.map(({ id }) => id).join() return $.ajax({ type: 'POST', url: 'https://scnu-exam.webtrn.cn/student/exam/examrecord_getRecordContent.action', headers: { Authority: 'scnu-exam.webtrn.cn', Pragma: 'no-cache', 'Cache-Control': 'no-cache', Accept: '*/*', 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'X-Requested-With': 'XMLHttpRequest', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36 Edg/93.0.961.47', Origin: 'https://scnu-exam.webtrn.cn', 'Sec-Fetch-Site': 'same-origin', 'Sec-Fetch-Mode': 'cors', 'Sec-Fetch-Dest': 'empty', Referer: `https://scnu-exam.webtrn.cn/student/exam/examrecord_recordDetail.action?recordId=${id}`, 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', Cookie: document.cookie, 'Accept-Encoding': 'gzip' }, contentType: 'application/x-www-form-urlencoded', data: { recordId: id, examBatchId: examBatchId, contentIds: contentIds, 'params.monitor': '', 'params.isRandomQuestion': '0' } }).then((data) => { data = JSON.parse(data) contentList.forEach((item) => { if (data?.data?.[item.id]) { data.data[item.id].type = item.name } }) if (data.data) { resolve(data.data) } else { resolve({}) } }) }) }) } function getHTML(data) { return data .map((item) => { return Object.values(item) .map((val) => { if (val.contentHtml) return val }) .filter((s) => s) }) .flat() .sort((a, b) => QUESTION_TYPE_SORT[a.type] > QUESTION_TYPE_SORT[b.type] ? 1 : -1 ) .map((val) => val.contentHtml) .join('') } main() })()