// ==UserScript== // @name 文才学堂学习插件 // @namespace https://bbs.tampermonkey.net.cn/ // @version 0.2.8 // @description 文才学堂学习的插件,自动播放视频,自动完成作业,一建批量评论,考试一键搜集答案和一键答题 // @author zhou // @match http://learning.wencaischool.net/* // @match https://learning.wencaischool.net/* // @require https://scriptcat.org/lib/881/1.2.0/script-statistics.js // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @antifeature tracking // ==/UserScript== (function () { 'use strict'; var $ = window.$; function sleep(t) { const _t = Date.now() while (Date.now() - _t < t) { } } try { new SC_Statistic({ key: "0ugkrbe0t1nt192l", }); } catch (e) { console.warn("statistic failed: ", e); } /**=========================批量评论start==================================== */ let $topciBtn function initTopic() { const $btn = window.$('
批量发话题
') window.$('.btnGroup').append($btn) $topciBtn = $btn $btn.on('click', () => { const content = $('#searchKey')?.val() || '好好学习,天天向上' autoSendTopic(5, content) }) } function autoSendTopic(len = 5, content = '好好学习,天天向上') { function addTopic(content, ifAsk = '0', articleImgs = '') { $topciBtn.text(`正在发表(${len})`) var params = { 'user_id': getQueryString('userId'), 'course_id': getQueryString('courseId'), 'school_code': getQueryString('schoolCode'), 'grade_code': getQueryString('gradeCode'), 'course_code': getQueryString('courseCode'), 'content': content, 'is_ask': ifAsk, 'img_url': articleImgs, 'time': 0 }, url = urlStart + '/forum_article.action?req=publishArticle', res = getData(url, params); console.log(len) len-- setTimeout(() => { if (len > 0) { addTopic(content, ifAsk, articleImgs) } else { alert('发表完成') location.reload() } }, 1000) } addTopic(content) } const topMatch = new URLPattern({ hostname: location.hostname, pathname: "*/coursePost/index.html" }) if (topMatch.test(location.href)) initTopic() /**=========================批量评论end==================================== */ /**=========================批量视频start==================================== */ let $videoBtn function initVideo() { const $btn = $('
一键批量完成
') $('.lefttop').append($btn).css({ 'text-align': 'center', height: "auto" }).find('.btn').css({ display: 'inline-block', margin: '5px 10px' }); $videoBtn = $btn $btn.on('click', () => { autoPlayList() }) } let autoPlayed = false function autoPlayList() { if (autoPlayed) return alert('程序正在执行,请稍等') autoPlayed = true function getChapterList() { var params = { 'course_id': getQueryString('course_id') } var url = urlStart + '/newApp_learn_course.action?req=getCourseScormItemList', res = getData(url, params), str = ''; if (res.code === 1000) { var resdata = dedata(res.data); list = resdata.listCourseLesson } } function getPower() { var url = urlStart + '/newApp_use_energy.action?req=getUserAuthority', res = getData(url); if (res.code === 1000) { var data = dedata(res.data) learning_user_id = data.userId } } var i = 0, learning_user_id = 3518, list = [] function useEnergy(siid, courseId) { var params = { 'learning_user_id': getQueryString('user_id'), 'course_id': courseId, 'type_code': 'progress', 'item_id': siid }, url = urlStart + '/newApp_use_energy.action?req=saveUseEnergyInfo', res = getData(url, params), ret = false; if (res.code == 1000) { ret = true; var data = dedata(res.data); $('#lastEnergy').text(data); } else { layer.msg(res.message, { time: 1000 }); } return ret; } function saveSubmit(item) { var params = { 'user_id': getQueryString('user_id'), 'course_id': item.courseId, 'time': item.timeLen, 'item_id': item.lessonId, 'view_time': item.timeLen, 'last_view_time': item.timeLen - 1, 'video_length': item.timeLen, 'learning_user_id': learning_user_id }, url = urlStart + '/learning.action?req=submitScormAndHistorySave', res = getData(url, params); if (res.code == 1000) { var data = dedata(res.data) if (data.isFinish) { console.log(item.lessonName + '完成') i++ playNext() } } } function autoPlay(item) { var energy = true if (item.useEnergyNum == 1) { energy = useEnergy(item.lessonId, item.courseId) } var t = setTimeout(() => { if (!energy) return false saveSubmit(item) }, 1000) } function playNext() { var item = list[i] if (!item) return alert('已全部完成,请切换下一个课程') $videoBtn.text(`已完成(${i + 1}/${list.length})`) if (item.isFinish || item.lessonType != 'scorm_content') { console.log(`=====${item.lessonName}=====`) i++ playNext() } else { autoPlay(item) } } getChapterList() getPower() playNext() } const VMatch = new URLPattern({ hostname: location.hostname, pathname: "*/courseware/index.html" }) if (VMatch.test(location.href)) initVideo() /**=========================批量视频end==================================== */ /**=========================批量作业start==================================== */ let $workBtn, worked = false, baseurl, answerObj = {}, sExamStatus, examid function initWork() { const $btn = $('一键完成作业') $('.func').after($btn) $btn.css('margin-left', '10px') $workBtn = $btn; $btn.bind('click', async () => { if (worked) return alert('正在做题中...') worked = true $workBtn.text('正在自动做题中...') const html = document.body.outerHTML const contentIds = html.match(/contentId="(\d+)"/gi).map(item => item.match(/\d+/)[0]) const examids = html.match(/examid\s?=\s?"(\d+)"/ig).map(item => /\d+/.exec(item)[0]) baseurl = $fn.getLearningWeb() // console.log(contentIds, examids) try { // examid = examids[0] // await autoWork(contentIds[0]) for (let i = 0; i < examids.length; i++) { examid = examids[i] await autoWork(contentIds[i]) $workBtn.text(`已完成${i + 1}/${examids.length}`) sleep(1000) } alert('已全部完成') } catch (e) { console.log(e) } // location.reload() }) } async function autoWork(sContentId) { const chech = await fetch(`${baseurl}/course/check_pre_index.jsp?content_id=${sContentId}`).then(res => res.text()) if (chech.indexOf('ERROR:') > -1) return alert('数据错误,请手动完成作业') const sUrl = baseurl + "/exam/portal/exam_info.jsp?content_id=" + sContentId + "&type=work&is_make_up=undefined"; let html = await fetch(sUrl).then(res => res.text()) html = html.replace(/\\/g, '') let scoreId = html.match(/sScoreId\s?=\s?"(\d+)"/)?.[1] sExamStatus = /sExamStatus\s?=\s?"(\w+)"/.exec(html)[1] const score = html.match(/(\d+)\(\1\/100\)/)?.[1] console.log(score, scoreId, sExamStatus) if (score > 90) return if (sExamStatus === 'reexamine') { // 查看答案 const params = $wapper.api.getURLParams($wapper.api.setQueryString("reexamine", '0', sUrl)) const answers = await viewAnswer(scoreId, params) answerObj[sContentId] = answers console.log(answers) if (!answers) return alert(`id=${sContentId}题目获取答案失败,请手动操作`) await doWork(sContentId) } else { // 先做一遍题 await doWork(sContentId) sleep(1000) await autoWork(sContentId) } } function disposeAnswer(ans = '') { if (ans.indexOf('答') === 0) return ans.substring(2) return /\[参考答案:([^\]]+)\]/i.exec(ans)?.[1] || '' } function disposeNames(list) { const nameM = [...new Set(list.map(name => /\w{28,}/.exec(name)[0]).filter(name => name.split('_').length >= 4))] let currentName = '', nameSet = new Set() nameM.forEach(name => { const cname = strDiff(currentName, name) cname ? nameSet.add(cname) : nameSet.add(name) if (cname === name) { currentName && nameSet.delete(currentName) } currentName = name }) return [...nameSet] } function strDiff(str1 = '', str2 = '') { const arr1 = str1.split('_'), arr2 = str2.split('_') const maxlen = Math.max(arr1.length, arr2.length) const minlen = Math.min(arr1.length, arr2.length) const retArr = [] for (let i = 0; i < maxlen; i++) { if (arr1[i] === arr2[i]) retArr.push(arr1[i]) } return retArr.length >= (minlen - 1) ? retArr.join('_') : '' } function viewAnswer(scoreId, params) { return new Promise(async (resolve) => { const viewUrl = `${baseurl}/exam/portal/view_answer.jsp?exam_id=${examid}&score_id=${scoreId}&${params}`; let html = await fetch(viewUrl).then(res => res.text()) html = html.replaceAll(/\\/g, '') const obj = {} const answerM = html.match(/\[参考答案:[^\]]+\]|答[::][^<]*/gi) let nameM = html.match(/(name|id)=\\*"?\w{28,}?\\*"?[ >]/gi) if (!(answerM && nameM)) throw new Error('答案获取失败,请手动做题') nameM = disposeNames(nameM) if (nameM.length !== answerM.length) { console.log(nameM, answerM) throw new Error('答案解析失败,请联系管理员') } let count = 0 nameM.forEach((name, i) => { obj[name] = disposeAnswer(answerM[i]) count++ }) resolve(count ? obj : null) }) } function doWork(sContentId) { return new Promise(async (resolve) => { const viewUrl = location.origin + $api.fn.getActionURL(`com.lemon.learning.exam.StudentExamAction?op=before_exam&exam_id=${examid}&reexam=${(sExamStatus == "reexamine" ? "1" : "0")}&script=parent.afterCheckExam()&type=work`); // return resolve(console.log(viewUrl)) await fetch(viewUrl) const params = `exam_id=${examid}&type=work&content_id=${sContentId}&type=work&is_make_up=undefined` await fetch(`${baseurl}/exam/portal/exam_console.htm?${params}`) await fetch(`${baseurl}/exam/portal/exam_left.jsp?${params}`) let html = await fetch(`${baseurl}/exam/portal/exam.jsp?${params}`).then(res => res.text()) html = html.replace(/\\/g, '') const answers = answerObj[sContentId] || {} const arr = [] let nameM = html.match(/keylist="\w{28,}"/gi) // console.log(nameM) const names = disposeNames(nameM.concat(Object.keys(answers))) // console.log(names) names.forEach(name => { arr.push(`${name}=${answers[name] || 'A'}`) }) await postWork(arr.join('&')) resolve() }) } function postWork(body) { return new Promise(async (resolve) => { const url = location.origin + $api.fn.getActionURL(`com.lemon.learning.exam.StudentExamAction?op=submit_exam&exam_id=${examid}&b_out=1&item_id=&type=work&r=${Math.random()}`) $.ajax({ url, type: 'post', data: body, headers: { 'Content-Type': 'application/x-www-form-urlencoded;' }, success(data) { resolve(true) }, error(e) { console.log(e) } }) }) } const WMatch = new URLPattern({ hostname: location.hostname, pathname: "*/learning/learn_homework.jsp" }) if (WMatch.test(location.href)) initWork() /**=========================批量作业end==================================== */ /**=========================考试辅助start==================================== */ function exam() { let QAMap = {}, autoStart = false, errIndex = [] const QAIndex = ['A', 'B', 'C', 'D', 'E', 'F', 'J'] try { QAMap = JSON.parse(sessionStorage.getItem('__QAMap__')) || {} } catch (error) { QAMap = {} } function createBtn(name = '按钮') { const div = document.createElement('div') div.setAttribute('class', 'btn submitB') div.innerHTML = name return div } // 根据题目生成答案 function answerText(q) { return q.replace(/(_+)\([\d\w]\)\1/, '?') } function labelClck(arr, els, i = 0) { return new Promise(resolve => { const ai = arr[i] const label = els[ai] if (!label) resolve(true) label?.click() setTimeout(async () => { const f = await labelClck(arr, els, i + 1) f && resolve(f) }, 200) }) } function radom(max = 10, min = 0) { return Math.floor(Math.random() * max - min) + min } async function simClick(el, qa = {}, index) { const box = el.querySelector('.ansbox') const isAnswer = qa.a ?? false let answer = qa.a // 选择的答题模式 if (box.classList.contains('radioAnswer')) { let qais const solutions = box.querySelectorAll('.perRad label') qais = answer?.split('').map(a => QAIndex.indexOf(a)) if (box.querySelector('input[type=checkbox]') && !qais) { const s = new Set() for (let j = 0; j < solutions.length; j++) { s.add(radom(solutions.length)) } qais = [...s] } qais = qais || [radom(solutions.length)] qa.a = qais.map(i => QAIndex[i]) await labelClck(qais, solutions) } else if (box.classList.contains('txtAnswer')) { // 解答题 const textarea = box.querySelector('.showTextarea textarea') textarea.value = answer ?? `答:${answerText(qa.q)}` qa.a = textarea.value textarea.dispatchEvent(new InputEvent('input')) textarea.dispatchEvent(new InputEvent('change')) } else if (box.classList.contains('inputAnswer')) { // 填空题 const textarea = box.querySelector('.perInp input') textarea.value = answer ?? answerText(qa.q) qa.a = textarea.value textarea.dispatchEvent(new InputEvent('input')) textarea.dispatchEvent(new InputEvent('change')) } console.log(`题目${index}答题成功! ${qa.a} `, isAnswer ? '' : '[智能填充]') return new Promise((resolve, reject) => { setTimeout(() => { resolve() }, 1000); }) } let qa_count = 0 async function autoQA(list, _i = 0, s = '') { const el = list[_i] if (!el) { if (!qa_count) { QAMap = {} alert('已全部完成' + (errIndex.length ? `\n 其中${errIndex}等题随机生成,请自行检查` : '')) } return } const index = _i + 1 const tmc = el?.querySelector('.tmc') const type = +tmc?.getAttribute('ttype') if ([12, 5].includes(type)) { let q = tmc.querySelector('.tmFillCont')?.innerHTML || '' q = q.substr(0, 32) const l = tmc.querySelectorAll('.stmc') qa_count += l.length await autoQA(l, 0, q) } else { let title = el.querySelector('.tmTitleTxt').innerHTML + s title = title.substr(0, 250) const qa = QAMap[title]; const no = el.querySelector('.tmTitle span')?.innerHTML || index if (!qa?.a) { errIndex.push(no) } await simClick(el, qa, no); } qa_count-- await autoQA(list, index, s) } function getqs(els) { const qas = [] function dfs(list, s = '') { list.forEach(el => { const tmc = el.querySelector('.tmc') const type = +tmc?.getAttribute('ttype') if ([12, 5].includes(type)) { let q = tmc.querySelector('.tmFillCont')?.innerHTML || '' q = q.substr(0, 32) const l = tmc.querySelectorAll('.stmc') return dfs(l, q) } let title = el.querySelector('.tmTitleTxt').innerHTML + s title = title.substr(0, 250) qas.push(title) }) } dfs(els) return qas } async function fillFn(e) { if (autoStart) return alert('正在做题,请等待系统做完后再操作!'); const list = document.querySelectorAll('.tmList') try { const qs = getqs(list) await fetchQueryQAList(qs); } catch (err) { console.log(err) throw new Error('没有答案') } errIndex = [] autoStart = true qa_count = list.length autoQA(list) } function pintQAFn() { console.log(Object.values(QAMap).map(qa => `${qa.q}:${qa.a}`)) } function collectionVue() { const comp = document.querySelector('#paperExam')?.__vue__ if (!comp) return false const answerList = comp.answerList const qas = [] answerList.forEach(item => { let q = '', a = '' if ([5, 12].includes(+item.itemType)) { let s = item.itemName.substr(0, 32) item.questions.forEach(row => { q = row.itemName + s q = q.substr(0, 250) a = row.itemAnswer?.map(o => o.optionContent).join('') const qa = { q, a } qas.push(qa) QAMap[q] = qa }) } else { q = item.itemName q = q.substr(0, 250) a = item.itemAnswer?.map(o => o.optionContent).join('') const qa = { q, a } qas.push(qa) QAMap[q] = qa } }) ajaxSaveQA(qas) sessionStorage.setItem('__QAMap__', JSON.stringify(QAMap)) alert('收集完成!') return true } function collectionFn() { if (collectionVue()) return const list = document.querySelectorAll('.tmList') let nota = false const qas = [] function dfs(list, s = '') { list.forEach(el => { const tmc = el.querySelector('.tmc') const type = +tmc?.getAttribute('ttype') if ([12, 5].includes(type)) { let q = tmc.querySelector('.tmFillCont')?.innerHTML || '' q = q.substr(0, 32) const l = tmc.querySelectorAll('.stmc') return dfs(l, q) } let title = el.querySelector('.tmTitleTxt').innerHTML + s title = title.substr(0, 250) const a = el.querySelector('.rightCont')?.innerHTML if (!a) return nota || (nota = true) qas.push({ q: title, a: a }) }) } dfs(list) if (nota) return alert('请在查看答案页面收集答案') ajaxSaveQA(qas) qas.forEach(o => { QAMap[o.q] = o }) sessionStorage.setItem('__QAMap__', JSON.stringify(QAMap)) alert('收集完成!') } function fetchQueryQAList(qs = []) { const obj = { s: 'SVIP.Ssanpi_MyApi.AListQaList', app_key: 'E3FDCFA1F60C0527D6711E9AD299EED6', return_data: '0', } const params = Object.keys(obj).map(key => { return `${key}=${obj[key]}` }) return new Promise((resolve, reject) => { if (Object.keys(QAMap).length) { return resolve(true); } GM_xmlhttpRequest({ url: 'http://hn216.api.yesapi.cn/?' + params.join('&'), method: 'POST', data: JSON.stringify({ data: qs }), responseType: 'json', onload(res) { const { response } = res console.log(response) if (response.ret == 200) { response.data.list.forEach(o => { QAMap[o.q] = o }) // if (Math.abs(response.data.list.length - qs.length) > 5) { // return reject(); // } resolve(true) } else { reject() } }, onerror(e) { console.log(e) reject() } }) }) } function ajaxSaveQA(list = []) { const arr = list.filter(item => !QAMap[item.q]) if (!arr.length) return GM_xmlhttpRequest({ url: 'http://hn216.api.yesapi.cn/api/App/Table/MultiCreate', method: 'post', data: JSON.stringify({ service: 'App.Table.MultiCreate', app_key: 'E3FDCFA1F60C0527D6711E9AD299EED6', return_data: '0', model_name: 'qaList', datas: arr }), responseType: 'json', onload(res) { const { response } = res if (response.ret === 200) { alert('服务器题库更新完成!') } } }) } let status = 0 function initBtn() { if (status) return const div = document.createElement('div') div.setAttribute('class', 'btngroup') const right = document.querySelector('#paperRight') const dataset = right.dataset right.appendChild(div) const collectionBtn = createBtn('收集答案') collectionBtn.addEventListener('click', collectionFn) const fillBtn = createBtn('填充答案') fillBtn.addEventListener('click', fillFn) const div2 = document.createElement('div') div2.setAttribute('class', 'btngroup') right.appendChild(div2) const radomBtn = createBtn('智能填充') radomBtn.addEventListener('click', fillFn) const plBtn = createBtn('打印答案') plBtn.addEventListener('click', pintQAFn) div2.append(radomBtn, plBtn) div.append(collectionBtn, fillBtn) Object.keys(dataset).forEach(key => { div.setAttribute(`data-${key}`, dataset[key]) div2.setAttribute(`data-${key}`, dataset[key]) radomBtn.setAttribute(`data-${key}`, dataset[key]) collectionBtn.setAttribute(`data-${key}`, dataset[key]) fillBtn.setAttribute(`data-${key}`, dataset[key]) plBtn.setAttribute(`data-${key}`, dataset[key]) }) status++ } const KMatch = new URLPattern({ hostname: location.hostname, pathname: "*/exam/index.html" }) if (KMatch.test(location.href)) document.addEventListener('click', initBtn) } exam() /**=========================考试辅助end==================================== */ })();