// ==UserScript==
// @name 智云枢多功能插件
// @namespace http://tampermonkey.net/
// @match https://online.zretc.net/
// @match https://online.zretc.net/*
// @run-at document-start
// @version 2.9
// @license MIT
// @author Berger
// @description 智云枢一键清除红点提示、一键签到、一键完成作业、一键考试、提前查看考试/作业分数
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @require https://registry.npmmirror.com/sweetalert2/10.16.6/files/dist/sweetalert2.min.js
// @require https://registry.npmmirror.com/hotkeys-js/3.13.3/files/dist/hotkeys.min.js
// @resource https://registry.npmmirror.com/sweetalert2/10.16.6/files/dist/sweetalert2.min.css
// ==/UserScript==
(function () {
'use strict';
const classList = []
const originOpen = XMLHttpRequest.prototype.open;
const utils = {
getValue(name) {
return GM_getValue(name);
},
setValue(name, value) {
GM_setValue(name, value);
},
// 响应拦截器
responseInterceptor(apiUrl) {
return new Promise((resolve, reject) => {
// 修改 open 方法
XMLHttpRequest.prototype.open = function (method, url) {
// 检查 URL 是否包含指定的 apiUrl
if (url.indexOf(apiUrl) !== -1) {
// 监听 readystatechange 事件
this.addEventListener('readystatechange', function () {
if (this.readyState === 4) { // 请求完成
// 获取响应体
const res = JSON.parse(this.responseText);
resolve(res); // 解析 Promise 并返回响应数据
}
});
}
// 调用原始的 open 方法
originOpen.apply(this, arguments);
};
});
// XMLHttpRequest.prototype.open = function (method, url) {
// if (url.indexOf(apiUrl) !== -1) {
// this.addEventListener('readystatechange', function () {
// if (this.readyState === 4) {
// // 返回响应体
// }
// })
// }
//
// originOpen.apply(this, arguments);
// }
},
async sendApi(apiUrl, options) {
return await (await fetch(apiUrl, { // 修改为实际请求的URL
method: options,
headers: {
'Content-Type': 'application/json',
"zretc-token": localStorage.getItem("zretc-token")
}
})).json()
},
async sendApiWithBody(apiUrl, options, body) {
return await (await fetch(apiUrl, { // 修改为实际请求的URL
method: options,
headers: {
'Content-Type': 'application/json',
"zretc-token": localStorage.getItem("zretc-token")
},
body: JSON.stringify(body)
})).json()
},
showSuccess(message) {
Swal.fire({
icon: "success",
title: message,
showConfirmButton: false,
timer: 1300
}).then(() => {
location.reload()
});
},
showSuccessNotReload(message) {
Swal.fire({
icon: "success",
title: message,
showConfirmButton: false,
timer: 1300
})
},
showError(message) {
Swal.fire({
icon: "error",
title: message,
showConfirmButton: false,
timer: 1300
})
},
showInfo(message) {
Swal.fire({
icon: "info",
title: message,
showConfirmButton: false,
timer: 1500
})
},
showLoad(msg) {
Swal.fire({
title: "请等待...",
html: msg,
didOpen: () => {
Swal.showLoading();
},
})
},
showDialog(title, text) {
Swal.fire({
title: title,
html: text,
icon: "question"
});
},
async getStudentId(courseId) {
let studentId = 0;
try {
const response = await this.sendApi(urlConstants.STUDENT_ID.replace('{}', courseId), 'GET');
studentId = response['data']['studentId'];
} catch (error) {
console.error('Error fetching student ID:', error);
}
return studentId;
},
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
},
// Levenshtein距离算法
levenshtein(a, b) {
const matrix = [];
// 初始化矩阵
for (let i = 0; i <= b.length; i++) {
matrix[i] = [i];
}
for (let j = 0; j <= a.length; j++) {
matrix[0][j] = j;
}
// 填充矩阵
for (let i = 1; i <= b.length; i++) {
for (let j = 1; j <= a.length; j++) {
if (b.charAt(i - 1) === a.charAt(j - 1)) {
matrix[i][j] = matrix[i - 1][j - 1];
} else {
matrix[i][j] = Math.min(
matrix[i - 1][j - 1] + 1, // 替换
Math.min(matrix[i][j - 1] + 1, // 插入
matrix[i - 1][j] + 1) // 删除
);
}
}
}
return matrix[b.length][a.length];
},
}
// utils.responseInterceptor('/submit-score')
// 创建清除小红点按钮
function createClearButton() {
let clearButton = document.createElement('div');
clearButton.innerHTML = '<button class="el-button el-button--danger el-button--small">清除小红点</button>';
let handleBox = document.querySelectorAll('.handle-box');
handleBox.forEach(function (divElement) {
divElement.appendChild(clearButton);
clearButton.addEventListener("click", async function () {
utils.showLoad("正在清理中,请等待!")
const response = await utils.sendApi(urlConstants.CLASS_LIST, 'GET')
const data = response['data']
for (let i = 0; i < data.length; i++) {
const instanceId = data[i]['id']
for (let j = 0; j < data[i]['backlogDTOS'].length; j++) {
const messageId = data[i]['backlogDTOS'][j]['id']
const handle_clear_url = urlConstants.CLEAR_MSG.replace('{}', instanceId).replace('{}', messageId)
// 清除消息功能
const clearResponse = await utils.sendApi(handle_clear_url, 'PUT')
if (clearResponse['code'] !== "1") {
return utils.showError("未知错误,请联系开发者!")
}
}
}
Swal.close(); // 关闭加载提示
utils.showSuccess("清除成功,即将刷新页面!")
})
});
}
// 创建一键签到按钮
function createSignButton() {
let signButton = document.createElement('div');
signButton.innerHTML = '<button class="el-button el-button--danger el-button--small" style="margin-left: 10px">一键签到</button>';
let illustrate = document.createElement('div')
illustrate.innerHTML = '<button class="el-button el-button--primary el-button--small" style="margin-left: 10px">签到说明</button>';
let intervalId = setInterval(function () {
let signBox = document.querySelectorAll('.sign-box .header');
if (signBox.length > 0) {
clearInterval(intervalId); // 停止定时器
signBox.forEach(function (divElement) {
divElement.appendChild(signButton)
divElement.appendChild(illustrate)
signButton.addEventListener("click", async function () {
utils.showInfo('正在签到中...')
const response = await utils.sendApi(urlConstants.SIGN_LIST.replace('{}', utils.getValue('courseId')), 'GET')
const signData = response['data'][0]
const signId = signData['id']
const courseId = utils.getValue('courseId')
const studentId = await utils.getStudentId(courseId)
urlConstants.SIGN_BODY.signInId = signId
urlConstants.SIGN_BODY.studentId = studentId
console.log(signId)
// 执行签到
const signResponse = await utils.sendApiWithBody(urlConstants.SIGN.replace('{}', courseId).replace('{}', signId), 'PATCH', urlConstants.SIGN_BODY)
if (signResponse['code'] === '1') {
utils.showSuccess('签到成功!即将刷新页面。')
await utils.sleep(1000)
window.location.reload()
}
})
illustrate.addEventListener("click", function () {
const illustrateText = `<p>1.签到结束了也可以签到</p><p>2.无需签到码</p><p>3.请低调使用</p>`
utils.showDialog('一键签到须知', illustrateText)
})
})
}
}, 1000); // 每隔 1 秒检查一次
}
// 创建一键完成视频任务按钮
function createCompleteVideoButton() {
let completeVideoButton = document.createElement('div');
completeVideoButton.innerHTML = '<button class="el-button el-button--danger el-button--small" style="margin: 20px 0 0 20px">一键完成所有视频任务</button>';
let intervalId = setInterval(function () {
let videoBox = document.querySelectorAll('.study-container');
if (videoBox.length > 0) {
clearInterval(intervalId); // 停止定时器
console.log(videoBox)
if (videoBox[0].firstChild) {
videoBox[0].insertBefore(completeVideoButton, videoBox[0].firstChild);
} else {
videoBox[0].appendChild(completeVideoButton);
}
completeVideoButton.addEventListener("click", async function () {
utils.showInfo("正在开发,请耐心等待!")
// utils.showLoad("正在完成视频,请耐心等待...")
// //获取当前url
// let currentURL = window.location.href;
// let parts = currentURL.split('/');
// let courseId = parts[parts.length - 2];
// const response = await utils.sendApi(urlConstants.VIDEO_LIST.replace("{}", courseId), 'GET')
// const data = response['data']
//
// for (let i = 0; i < data.length; i++) {
// const instanceChapterId = data[i]['instanceChapterId']
// const instanceId = data[i]['instanceId']
// const resourceId = data[i]['resourceId']
// const sendBody = urlConstants.COMPLETE_VIDEO_BODY
// sendBody.instanceId = instanceId
// sendBody.instanceChapterId = instanceChapterId
// sendBody.resourceId = resourceId
//
// // console.log(sendBody)
//
// const clearResponse = await utils.sendApiWithBody(urlConstants.VIDEO_LIST.replace("{}", instanceId), 'POST', sendBody)
// if (clearResponse['code'] !== "1") {
// return utils.showError("未知错误,请联系开发者!")
// }
//
// }
// Swal.close(); // 关闭加载提示
// utils.showSuccess("清除成功,即将刷新页面!")
})
}
}, 1000); // 每隔 1 秒检查一次
}
// 作业答题页面创建按钮
function createCompleteHomeWorkButton() {
let completeHomeworkDiv = document.createElement('div');
completeHomeworkDiv.innerHTML = `<div style="background-color: #FFFFFF;margin-bottom: 10px">
<button class="el-button el-button--danger el-button--small" style="margin: 10px">一键完成</button>
<div role="switch" class="el-switch">
<span style="margin-right: 5px">自动提交 </span>
<input type="checkbox" class="el-switch__input"><span class="el-switch__core" style="width: 40px;"></span></div>
</div>`
let intervalId = setInterval(async function () {
const currentUrl = window.location.href
let homeworkBox = document.querySelectorAll('.right-box');
if (homeworkBox.length > 0 && ((currentUrl.indexOf('&homeworkId=') !== -1) || (currentUrl.indexOf('/exams/') !== -1))) {
clearInterval(intervalId); // 停止定时器
if (homeworkBox[0].firstChild) {
homeworkBox[0].insertBefore(completeHomeworkDiv, homeworkBox[0].firstChild);
} else {
homeworkBox[0].appendChild(completeHomeworkDiv);
}
let autoSubmitSwitch = completeHomeworkDiv.querySelector('.el-switch')
autoSubmitSwitch.addEventListener("click", function () {
utils.showDialog("自动提交风险提示", "本项目只适配了<选择题、填空题、多选题>,其他题型暂未适配,为保证答题安全,暂时不允许开放此项,请检查后手动提交作业!")
})
let completeHomeworkDivButton = completeHomeworkDiv.querySelector('.el-button')
completeHomeworkDivButton.addEventListener("click", async function () {
utils.showLoad("正在完成作业,请耐心等待....")
const urlObj = new URL(currentUrl);
let homeworkDetail = ''
if ((currentUrl.indexOf('/exams/') !== -1)) {
// // 获取当前页面的URL
// const currentURL = window.location.href;
const regex = /\/exams\/(\d+)\?/;
const match = currentUrl.match(regex);
// alert(match[1])
homeworkDetail = await utils.sendApi(urlConstants.HOMEWORK_DETAIL.replace("{}", match[1]), 'GET')
} else {
const homeworkId = urlObj.searchParams.get("homeworkId");
homeworkDetail = await utils.sendApi(urlConstants.HOMEWORK_DETAIL.replace("{}", homeworkId), 'GET')
}
const contentObjDTOList = homeworkDetail['data']['contentObjDTOList']
console.log(contentObjDTOList)
// 循环后端返回来的题目
contentObjDTOList.forEach(function (contentObjDTO, index) {
// 题目类型
const questionType = contentObjDTO['queType']
// 答案选项
const answersOptionByResponse = contentObjDTO['answer']
// 答案选项列表 如A、B、C、D
const questionOptionsListByResponse = contentObjDTO['questionOptions']
let temptDivResponse = document.createElement('div')
// 后端返回来的题目
temptDivResponse.innerHTML = contentObjDTO['content']
const questionByResponseHandle = temptDivResponse.textContent
// console.log(questionByResponseHandle)
// 页面上的题目DIV
const questionDivByPageList = document.querySelectorAll(".question")
for (let i = 0; i < questionDivByPageList.length; i++) {
//题目内容
const questionByPageList = questionDivByPageList[i].querySelector('.que-title > .title-box >.ck-content')
const questionByPageHandle = questionByPageList.textContent
// 计算Levenshtein距离
const distance = utils.levenshtein(questionByResponseHandle, questionByPageHandle);
const maxLength = Math.max(questionByResponseHandle.length, questionByPageHandle.length);
const similarity = 1 - (distance / maxLength);
let answerContent = ''
let answerContentHandle = ''
// 检查相似度 校验页面题目和后端题目
if (similarity > 0.7) { // 0.8为相似度阈值,可以调整
if (questionType === 1 || questionType === 3) {
for (let j = 0; j < questionOptionsListByResponse.length; j++) {
if (answersOptionByResponse === questionOptionsListByResponse[j]['optKey']) {
answerContent = questionOptionsListByResponse[j]['content']
let answerDivTempt = document.createElement('div')
answerDivTempt.innerHTML = answerContent
answerContentHandle = answerDivTempt.textContent
// console.log(answerContentHandle)
break
}
}
// 页面选项
const optionsListByPage = questionDivByPageList[i].querySelector('.option-list')
const optionItem = optionsListByPage.querySelectorAll('.ck-content')
for (let optionIndex = 0; optionIndex < optionItem.length; optionIndex++) {
// console.log(optionItem[optionIndex].textContent)
if (answerContentHandle === optionItem[optionIndex].textContent) {
optionItem[optionIndex].click()
break
}
}
} else if (questionType === 4) {
// 填空题
//获取input
const inputList = questionDivByPageList[i].querySelectorAll(".el-input__inner")
// console.log(answersOptionByResponse)
const answer = JSON.parse(answersOptionByResponse).map(item => item.answer);
inputList.forEach(function (inputElement) {
const inputEvent = document.createEvent('Event');
inputEvent.initEvent('input', true, true);
inputElement.value = answer;
inputElement.dispatchEvent(inputEvent);
const changeEvent = document.createEvent('Event');
changeEvent.initEvent('change', true, true);
inputElement.dispatchEvent(changeEvent);
})
} else if (questionType === 2) {
// 多选题
console.log(questionOptionsListByResponse)
console.log(answersOptionByResponse)
let answerHandleArray = new Set()
for (let j = 0; j < answersOptionByResponse.length; j++) {
for (let k = 0; k < questionOptionsListByResponse.length; k++) {
if (answersOptionByResponse.charAt(j) === questionOptionsListByResponse[k]['optKey']) {
// 包含 HTML 实体编码的文本
// const encodedText = '<p><span style="background-color:rgb(253,253,254);color:rgb(5,7,59);"><result></span></p>';
const tempDiv = document.createElement('div');
tempDiv.innerHTML = questionOptionsListByResponse[k]['content'];
// const decodedText = tempDiv.innerHTML;
// console.log(tempDiv.textContent)
answerHandleArray.add(tempDiv.textContent)
}
}
}
// console.log(answerHandleArray)
// console.log("================================")
// 页面选项
const optionsListByPage = questionDivByPageList[i].querySelector('.option-list')
const optionItem = optionsListByPage.querySelectorAll('.ck-content')
for (let optionIndex = 0; optionIndex < optionItem.length; optionIndex++) {
// console.log(optionItem[optionIndex].textContent)
answerHandleArray.forEach(function (answerItem) {
const answerHandlePage = optionItem[optionIndex].textContent || optionItem[optionIndex].innerText;
// console.log(answerItem)
if (answerItem === answerHandlePage) {
optionItem[optionIndex].click()
}
})
}
}
break
}
}
})
utils.showSuccessNotReload("自动答题成功,请检查!")
})
}
}, 1000); // 每隔 1 秒检查一次
}
function createQueryScoreInfo() {
let queryScoreButton = document.createElement('div');
utils.responseInterceptor('/submit-score').then(scoreResponse => {
let score = scoreResponse['data'][0]['score']
queryScoreButton.innerHTML = `<div data-v-439af87f="" class="instruction update-score"><p>
当前试卷得分:${score} 分</p></div>`;
})
let intervalId = setInterval(function () {
let videoBox = document.querySelectorAll('.list-bg');
if (videoBox.length > 0) {
clearInterval(intervalId); // 停止定时器
const scoreDiv = document.querySelectorAll('.update-score')
if (scoreDiv.length > 0){
scoreDiv[0].remove()
}
videoBox[2].appendChild(queryScoreButton)
}
}, 1000); // 每隔 1 秒检查一次
}
let main = {
init() {
createSignButton()
createClearButton()
createCompleteVideoButton()
createCompleteHomeWorkButton()
const currentUrl = window.location.href
if (currentUrl.indexOf('/course/student/courses') !== -1) {
const strings = currentUrl.split('/');
strings.forEach(function (key, index) {
if (key.length > 14 && key > 3) {
utils.setValue("courseId", key)
console.log("courseId", key)
}
})
}
}
}
window.addEventListener('load', main.init);
window.addEventListener('load', function () {
// 监听 Vue 实例的创建
const vueInstanceObserver = new MutationObserver(function (mutationsList, observer) {
mutationsList.forEach(mutation => {
// 如果新增了一个节点,并且这个节点是 Vue 实例所在的 DOM 元素
if (mutation.type === 'childList' && mutation.addedNodes.length > 0 && mutation.addedNodes[0].__vue__) {
createQueryScoreInfo()
}
});
});
vueInstanceObserver.observe(document.body, {childList: true, subtree: true});
})
class urlConstants {
static CLASS_LIST = "https://api.zretc.net/instances/instances/stu/my-instance?pageSize=10&pageNum=1&instanceStatus&orderBy=1&orderByWay=DESC"
static CLEAR_MSG = "https://api.zretc.net/instances/instances/{}/backlog-message/stu/{}"
// 签到列表
static SIGN_LIST = "https://api.zretc.net/instances/instances/{}/sign/stu/page?signInName=&stuSignInType=&pageSize=20&pageNum=1"
// 签到
static SIGN = "https://api.zretc.net/instances/instances/{}/sign/{}/edit-stu-sign"
// 签到请求体
static SIGN_BODY = {
"signInId": "",
"studentId": "",
"remark": "", // 备注
"stuSignInType": 1 // 签到状态 4=请假 2=迟到 1=签到成功 3=早退 0=未签到
}
// 作业列表
static HOMEWORK_LIST = "https://api.zretc.net/instances/instances/{}/stu/homework-list"
// 获取作业答案
static HOMEWORK_DETAIL = "https://api.zretc.net/homework/homeworks/{}/detail-objective-list"
//作业提交
static HOMEWORK_SUBMIT = "https://online.zretc.net/api/homework/submits?submitType=1"
// 获取studentId url
static STUDENT_ID = "https://api.zretc.net/instances/instances/stu/my-progress/{}"
static VIDEO_LIST = "https://api.zretc.net/instances/instances/{}/student-study-record"
static COMPLETE_VIDEO_BODY = {
"status": 2,
"instanceChapterId": "1795336607034122241", //
"instanceId": "1760130398605942786", //
"resourceCategory": 1,
"resourceId": "1795336675761987585", //
"resourceLocation": 9999999,
"resourceTotal": 9999999
}
/**
* resourceCategory分类
* 附件 6
* 视频 1
* 作业 3
*/
}
})();