// ==UserScript==
// @name 智云枢多功能插件
// @namespace http://tampermonkey.net/
// @match https://www.omniedu.com/*
// @run-at document-start
// @version 2.97
// @license MIT
// @author Berger
// @description 智云枢一键签到、一键完成作业、一键考试、提前查看考试/作业分数
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addStyle
// @grant GM_getResourceText
// @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 sweetalert2CSS https://registry.npmmirror.com/sweetalert2/10.16.6/files/dist/sweetalert2.min.css
// ==/UserScript==
(function () {
'use strict';
GM_addStyle(GM_getResourceText("sweetalert2CSS"));
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);
};
});
},
modifyResponse(apiUrl, key, value) {
XMLHttpRequest.prototype.open = function (method, url) {
console.log(url.indexOf(apiUrl))
if (url.indexOf(apiUrl) !== -1) {
// 监听 readystatechange 事件
this.addEventListener('readystatechange', function () {
if (this.readyState === 4) { // 请求完成
// 获取响应体
const res = JSON.parse(this.responseText);
Object.defineProperty(this, "response", {
writable: true,
});
res['data'][key] = value
this.responseText = res
console.log('res', res)
}
});
}
// 调用原始的 open 方法
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];
},
}
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 createCompleteHomeWorkButton() {
let completeHomeworkDiv = document.createElement('div');
completeHomeworkDiv.style.display = 'block'
completeHomeworkDiv.innerHTML = `<div style="background-color: #FFFFFF;margin-bottom: 10px">
<button class="el-button el-button--danger el-button--small" style="margin: 10px">一键完成</button>
<span style="font-size: 12px">H键自动隐藏/显示本窗口 </span>
</div>`
// 添加快捷键
document.addEventListener('keydown', function (event) {
if (event.key === 'h' || event.key === 'H') {
if (completeHomeworkDiv.style.display === 'none') {
completeHomeworkDiv.style.display = 'block';
} else {
completeHomeworkDiv.style.display = 'none';
}
}
});
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 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']
if (score) {
queryScoreButton.innerHTML = `<div data-v-0d315cdc="" 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()
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 => {
if (mutation.type === 'childList' && mutation.addedNodes.length > 0 && mutation.addedNodes[0].__vue__) {
createQueryScoreInfo()
// createCompulsiveFinishWorkButton()
}
});
});
vueInstanceObserver.observe(document.body, {childList: true, subtree: true});
})
class urlConstants {
// 签到列表
static SIGN_LIST = "https://api.omniedu.com/instances/instances/{}/sign/stu/page?signInName=&stuSignInType=&pageSize=20&pageNum=1"
// 签到
static SIGN = "https://api.omniedu.com/instances/instances/{}/sign/{}/edit-stu-sign"
// 签到请求体
static SIGN_BODY = {
"signInId": "",
"studentId": "",
"remark": "", // 备注
"stuSignInType": 1 // 签到状态 4=请假 2=迟到 1=签到成功 3=早退 0=未签到
}
// 获取作业答案
static HOMEWORK_DETAIL = "https://api.omniedu.com/homework/homeworks/{}/detail-objective-list"
// 获取studentId url
static STUDENT_ID = "https://api.omniedu.com/instances/instances/stu/my-progress/{}"
}
})();