// ==UserScript== // @name 职教云题库导出脚本 // @namespace https://schhz.cn // @version 0.1.5 // @description 获取职教云题库的题目 // @author schlibra // @match https://spoc-exam.icve.com.cn/student/exam/examrecord_recordDetail.action* // @match https://course.icve.com.cn/* // @match https://user.icve.com.cn/* // @require https://unpkg.com/sweetalert@2.1.2/dist/sweetalert.min.js // @grant GM_setValue // @grant GM_getValue // @grant GM_info // ==/UserScript== // 脚本版本 const version = GM_info.script.version; // 定义无用关键字,从题目和答案中替换 const answerUnuseWord = ["(1 分)", "(2 分)", "(3 分)", "(4 分)", "(5 分)", "(1分)", "(2分)", "(3分)", "(4分)", "(5分)", "\n", " ", " ", decodeURI("%C2%A0")]; // 存储标记前缀 const storagePrefix = "sch_ocs_export_"; // 创建按钮 let button = document.createElement("div"); // 定义数据存储函数 function setData(key = "", value = "") { GM_setValue(storagePrefix + key, value); } function getData(key = "", defaults = "") { return GM_getValue(storagePrefix + key, defaults); } +(function () { 'use strict'; checkVersion(); createButton(); })(); // 检测脚本版本更新 function checkVersion(){ $.get("https://search.schhz.cn/version/",res=>{ if(res.code){ if(res.version!=version&&res.version!=getData("ignore_update_version")){ swal({ title: "检测到版本更新", text: `检测到新版本:${res.version},当前脚本版本:${version}\n版本更新日志:${res.log}`, icon: "info", closeOnClickOutside: false, closeOnEsc: false, buttons: { ignore: { text: "忽略此版本更新", value: "ignore" }, update: { text: "立即更新", value: "update" }, cancel: { text: "关闭", value: "cancel", visible: true } } }).then(value=>{ switch(value){ case "ignore": setData("ignore_update_version", res.version); break; case "update": let link = res.url[GM_info.scriptHandler]; window.open(link,"_blank"); break; } }) } }else{ swal({ title: "版本检查失败", text: "无法从更新服务器获取到版本信息,请稍后尝试", icon: "error", buttons: ["确定", "关闭"] }) } }) } // 创建脚本功能按钮,显示在左上角 function createButton() { // 设置按钮样式 button.style.width = "50px"; button.style.height = "50px"; button.style.borderRadius = "50%"; button.style.backgroundColor = "white"; button.style.boxShadow = "0 0 24px -12px #3f3f3f"; button.style.position = "fixed"; button.style.zIndex = "9999"; button.style.left = `${getData("posX", 100)}px`; button.style.top = `${getData("posY",100)}px`; button.style.textAlign = "center"; button.style.lineHeight = "50px"; button.style.color = "#3624ff"; button.style.fontSize = "20px"; button.style.transition = "all .5s"; button.innerText = getData("text", "S"); // START: 设置鼠标悬浮效果 button.onmouseover = e => { button.style.backgroundColor = "#eee" button.style.color = "deepskyblue" } button.onmouseout = e => { button.style.backgroundColor = "white" button.style.color = "#3624ff" } // END // START: 设置鼠标按住效果 button.onmousedown = e => { button.style.backgroundColor = "#ccc" button.style.color = "blue" } button.onmouseup = e => { button.style.backgroundColor = "white" button.style.color = "#3624ff" } // END // 绑定点击事件,展示主窗口 button.onclick = showMain // 追加按钮到页面 document.body.appendChild(button); } // 显示主窗口 function showMain() { swal({ title: "职教云题库导出工具", text: "请在下方选择一个操作", closeOnClickOutside: false, closeOnEsc: false, buttons: { exp: { text: "导出题目", value: "export" }, settings: { text: "脚本设置", value: "settings" }, cancel: { text: "关闭", value: "cancel", visible: true } } }).then(value => { switch (value) { case "export": exportWindow(); break; case "settings": settingsWindow(); break; } } ) } function exportWindow() { if(location.href.startsWith("https://spoc-exam.icve.com.cn/student/exam/examrecord_recordDetail.action")){ let length = $(".q_content").length; let rightLength = $("span[name='rightAnswer']").length; if (length == rightLength) { swal({ title: "导出确认", text: `这套题一共有 ${length}道题,并且有正确答案,即将导出全部题目`, closeOnClickOutside: false, closeOnEsc: false, buttons: { confirm: { text: "导出", value: "confirm" }, back: { text: "返回", value: "back" }, cancel: { text: "关闭", value: "cancel", visible: true } } }).then(value => { if (value == "confirm") { exportWindowHasAnswer(length) }else if(value== "back"){ showMain() } }) } else { swal({ title: "导出确认", text: `这套题一共有 ${length}道题,但是没有正确答案,即将导出您选择正确的题目`, closeOnClickOutside: false, closeOnEsc: false, buttons: { confirm: { text: "导出", value: "confirm" }, back: { text: "返回", value: "back" }, cancel: { text: "关闭", value: "cancel", visible: true } } }).then(value => { if (value == "confirm") { exportWindowNoAnswer(length) }else if(value == "back"){ showMain() } }) } }else{ swal({ title: "无法导出", text: "导出题目功能只能在作业详情中使用,该页面不支持导出题目", closeOnClickOutside: false, closeOnEsc: false, icon: "error", buttons: { back: { text: "返回", value: "back" }, cancel: { text: "关闭", value: "cancel", visible: true } } }).then(value=>{ if(value=="back"){ showMain(); } }) } } function removeUnuseWord(text) { answerUnuseWord.forEach(word => { text = text.replaceAll(word, "") } ); for (let i = 65; i <= 90; ++i) { text = text.replaceAll(`${String.fromCharCode(i)}.`, "") } return text; } function exportWindowHasAnswer(length) { let list = []; for (let i = 0; i < length; ++i) { let title = removeUnuseWord($(".divQuestionTitle")[i].innerText.replace(`${i + 1}、`, "")) let answer = [] $("span[name='rightAnswer']")[i].innerText.split("").forEach(item => { answer.push($(".questionOptions")[i].children[item.charCodeAt(0) - 65].innerText) }) answer = removeUnuseWord(answer.join("#")); list.push({ title, answer }); } console.log(list) exportResultWindow(list); } function exportWindowNoAnswer(length) { let list = []; for (let i = 0; i < length; ++i) { let ans_obj = $(".exam_answers_tit")[i].children; console.log(ans_obj[1].classList[1]); if (ans_obj[1].classList[1] == "icon_examright") { console.log(ans_obj) let title = removeUnuseWord($(".divQuestionTitle")[i].innerText.replace(`${i + 1}、`, "")) let answer = [] ans_obj[0].innerText.split("").forEach(item => { answer.push($(".questionOptions")[i].children[item.charCodeAt(0) - 65].innerText) }) answer = removeUnuseWord(answer.join("#")) list.push({ title, answer }); } } console.log(list); exportResultWindow(list); } function exportResultWindow(list=[]){ let count = list.length; let content = document.createElement("textarea"); content.setAttribute("rows", 10); content.setAttribute("cols", 50); content.value = JSON.stringify(list); swal({ title: "导出结果", text: `题目导出完成,共导出${count}题,结果如下`, content, closeOnClickOutside: false, closeOnEsc: false, buttons: { upload: { text: "上传", value: "upload" }, copy: { text: "复制", value: "copy", closeModal: false }, back: { text: "返回", value: "back" }, cancel: { text: "关闭", value: "cancel", visible: true } } }).then(value=>{ switch(value){ case "copy": content.select(); document.execCommand("copy"); swal.stopLoading(); break; case "back": showMain(); break; case "upload": uploadFunction(list); break; } }) } function uploadFunction(list=[]){ let server = getData("server", ""); let token = getData("token", ""); if(server === ""){ swal({ title: "上传失败", text: "还没有正确配置服务器地址,暂时不能上传题目", icon: "error", closeOnClickOutside: false, closeOnEsc: false, buttons: { settings: { text: "设置服务器", value: "settings" }, back: { text: "返回", value: "back" }, cancel: { text: "关闭", value: "cancel", visible: true } } }).then(value=>{ switch(value){ case "settings": settingsUploadWindow(); break; case "back": showMain(); break; } }) }else{ let data = JSON.stringify(list); $.post(`${server}/api/?action=import&type=multi&token=${token}`,{data},res=>{ if(res.code){ swal({ title: "上传成功", text: res.msg, icon: "success", closeOnClickOutside: false, closeOnEsc: false, buttons: ["确定", "关闭"] }) }else{ swal({ title:"上传失败", text: res.msg ?? "服务器没有正常返回", icon: "error", closeOnEsc: false, closeOnClickOutside: false, buttons: ["确定", "取消"] }) } }) } } function settingsWindow() { swal({ title: "设置页面", text: "在这里修改你的设置", closeOnClickOutside: false, closeOnEsc: false, buttons: { about: { text: "关于脚本", value: "about" }, upload: { text: "题库上传", value: "upload" }, logo: { text: "按钮设置", value: "logo" }, back: { text: "返回", value: "back" }, cancel: { visible: true, text: "关闭", value: "cancel" } } }).then(value => { switch (value) { case "about": settingsAboutWindow(); break; case "upload": settingsUploadWindow(); break; case "logo": settingsLogoWindow(); break; case "back": showMain(); break; } }) } function settingsAboutWindow() { swal({ title: "关于脚本", text: `职教云题库导出脚本,用于一键将职教云题目导出,同时支持自建题库,并一键上传至自建题库中\n脚本版本:${version}`, icon: "info", closeOnClickOutside: false, closeOnEsc: false, buttons: { back: { text: "返回", value: "back" }, cancel: { text: "关闭", value: "cancel", visible: true } } }).then(value=>{ if(value=="back"){ settingsWindow() } }) } function settingsUploadWindow() { let server = getData("server", ""); let token = getData("token",""); let content = document.createElement("div"); content.style.textAlign="left" content.innerHTML = `

token用于在调用上传接口时鉴权使用,如果自己实现了接口但是没有使用到token,可以不填写这个值

`; swal({ title: "设置上传服务器", text: "设置题库上传服务器,将导出的题库一键上传到题库服务器中,在下面填写服务器地址,开头需要加协议名,链接结尾不需要加斜杠", content, closeOnClickOutside: false, closeOnEsc: false, buttons: { confirm: { text:"确定", value: "confirm" }, back: { text: "返回", value: "back" }, cancel: { text: "关闭", value: "cancel", visible: true } } }).then(value=>{ switch(value){ case "confirm": setData("token", $(`#${storagePrefix}token_input`).val()); settingServer($(`#${storagePrefix}server_input`).val()); break; case "back": settingsWindow(); break; } }) } function settingServer(server=""){ if(server.startsWith("https://") && (! server.endsWith("/"))){ $.get(`${server}/status/`, data=>{ if(data.code){ setData("server", server); swal({ title: "设置成功", text: `服务器设置成功,服务器返回消息:${data.msg}`, closeOnClickOutside: false, closeOnEsc: false, buttons: { back: { text: "返回", value: "back" }, cancel: { text: "关闭", visible: true, value: "cancel" } } }).then(value=>{ if(value=="cancel"){ settingsWindow() } }) }else{ swal({ title: "错误提示", text: "服务器没有正常返回结果,您是否要继续设置该地址?", closeOnClickOutside: false, closeOnEsc: false, buttons: { confirm: { text: "确定", value: "confirm" }, back: { text: "返回", value: "back" }, cancel: { text: "关闭", visible: true, value: "cancel" } } }).then(value=>{ if(value=="confirm"){ setData("server", server) }else if(value=="back"){ settingsUploadWindow() } }) } }) }else{ swal({ title: "错误提示", text: "您设置的服务器地址不符合要求,请重新设置", closeOnClickOutside: false, closeOnEsc: false, buttons: { back: { text:"返回", value: "back" }, cancel: { text: "关闭", value: "cancel", visible: true } } }).then(value=>{ if(value=="back"){ settingsUploadWindow(); } }) } } // 按钮设置 function settingsLogoWindow() { let text = getData("text", "S"); let posX = getData("posX", 100); let posY = getData("posY", 100) let dom = document.createElement("div"); dom.innerHTML = `

`; swal({ title: "按钮设置", text: "修改按钮文字和位置", content: dom, closeOnClickOutside: false, closeOnEsc: false, buttons: { confirm: { text: "确定", value: "confirm" }, back: { text: "返回", value: "back" }, cancel: { text: "关闭", value: "cancel", visible: true } } }).then(value => { if (value == "confirm") { setData("text", $(`#${storagePrefix}text_input`).val()); setData("posX", $(`#${storagePrefix}posX_input`).val()); setData("posY", $(`#${storagePrefix}posY_input`).val()); button.innerText = getData("text"); button.style.left = getData("posX")+"px"; button.style.top = getData("posY")+"px"; }else if(value == "back"){ settingsWindow() } }) }