// ==UserScript==
// @name 职教云题库导出脚本
// @namespace https://schhz.cn
// @version 0.1.3
// @description 获取职教云题库的题目
// @author schlibra
// @match https://spoc-exam.icve.com.cn/student/exam/examrecord_recordDetail.action*
// @require https://unpkg.com/sweetalert@2.1.2/dist/sweetalert.min.js
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==
// 脚本版本
const version = "0.1.3"
// 定义无用关键字,从题目和答案中替换
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';
createButton();
})();
// 创建脚本功能按钮,显示在左上角
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.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() {
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()
}
})
}
}
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 = `