// ==UserScript==
// @name 雨课堂小助手
// @namespace http://tampermonkey.net/
// @version 1.1
// @description 适配FJNU
// @author 原作者:曦月
// @license MIT
// @match https://*.yuketang.cn/pro/lms/*/homework/*
// @match https://*.yuketang.cn/pro/lms/*/studycontent
// @match https://*.yuketang.cn/pro/lms/*/video/*
// @require https://lib.baomitu.com/axios/0.27.2/axios.min.js
// @grant none
// ==/UserScript==
(function () {
'use strict';
// ajax监听列表
const ajaxList = [];
const listenList = [];
const div = document.createElement("div")
div.innerHTML = html
document.body.appendChild(div)
const showEl = document.querySelector(".title"),
showEl2 = document.querySelector(".press")
const url = location.pathname;
console.log("脚本运行")
// 同时播放视频数量
const videoNum = 5;
// 习题目录
const exerciseList = new RegExp(/get_exercise_list\/.+term/)
// 习题页面
const homework = new RegExp(/lms\/.+\/homework\//)
// 学习内容
const studycontent = new RegExp(/lms\/.+\/studycontent/)
// 学习列表
const studyList = new RegExp(/\/lms\/learn\/course\/chapter.+/)
// 完成进度
const progress = new RegExp(/\lms\/learn\/course\/schedule/)
// 视频播放
const videoPlay = new RegExp(/\lms\/.+video\/.+/)
// 获取视频链接
const getVideoUrl = new RegExp(/audiovideo\/playurl/)
// 视频进度提交
const videoPress = new RegExp(/video-log\/heartbeat/)
// 房间id
const classroom_id = +location.pathname.split("/")[4];
if (homework.test(url)) {
console.log("习题页面")
answerQuestions();
} else if (studycontent.test(url)) {
console.log("学习内容")
studycontentList();
} else if (videoPlay.test(url)) {
console.log("视频播放")
playVideo();
}
// 答题页面逻辑
function answerQuestions() {
const API_HEADER = localStorage.getItem("API_HEADER")
let universityId = null
if (API_HEADER) {
universityId = JSON.parse(API_HEADER).school_id;
} else {
alert("缺少运行必要参数");
return;
}
const ansUrl = `https://uestcedu.yuketang.cn/mooc-api/v1/lms/exercise/problem_apply/?term=latest&uv_id=${universityId}`
// 题目处理
async function ans(data) {
let answ = data.res.data.problems
console.log("所有题目", answ)
for (let key = 0; key < answ.length; key++) {
const el = answ[key];
// 过滤已经答对的题目
if (!el.user.is_show_answer) {
showEl.innerText = `正在答题 [${key + 1}]`
if (el.content.Type === "MultipleChoice") {
// 多选
console.log(`[${key + 1}]多选题:${el.content.Body}`)
showEl2.innerText = ""
await goAnas(el.content, true)
} else {
// 单选
console.log(`[${key + 1}]单选题:${el.content.Body}`)
showEl2.innerText = ""
await goAnas(el.content, false)
}
} else {
console.log("题目已答对")
}
}
console.log("所有题目已完成")
showEl.innerText = "所有题目已完成"
showEl2.innerText = ""
}
// 答题
async function goAnas(data, isMultip) {
const ansList = ansArr(data.Options, isMultip)
for (let key = 0; key < ansList.length; key++) {
await delay(3000);
const res = await axios.post(ansUrl, {
answer: ansList[key],
classroom_id,
problem_id: data.ProblemID
}, { headers: { 'xtbz': 'cloud' } })
if (res.data.data.is_right || res.data.data.is_correct) {
console.log(`尝试[${key + 1}/${ansList.length}] ${ansList[key]} 成功`)
showEl2.innerText = `尝试[${key + 1}/${ansList.length}] ${ansList[key]} 成功`
break;
} else {
console.log(`尝试[${key + 1}/${ansList.length}] ${ansList[key]} 失败`)
showEl2.innerText = `尝试[${key + 1}/${ansList.length}] ${ansList[key]} 失败`
}
}
}
// 请求间隔
function delay(time) {
return new Promise((res, rej) => {
setTimeout(() => {
res()
}, time)
})
}
// 生成所有答案组合
function ansArr(arr, isMultip) {
let N = null, answerArr = arr.map(el => el.key);
if (isMultip) {
N = answerArr.length;
} else {
N = 1;
}
let newAns = [];
if (N === 1) {
newAns = newAns.concat(combine(answerArr, 1))
} else {
for (let i = 2; i <= N; i++) {
newAns = newAns.concat(combine(answerArr, i))
}
}
return newAns;
function combine(arr, N) {
//存放索引号
let res = []
//存放最后的结果
var stack = []
arrayN(arr, 0, N, N, res, stack)
// return pedding(stack); // 如果下面返回的答案全都不对再启用这个,上面的函数也要改
return stack;
}
// 中间方法
function pedding(arr) {
let newArr = [];
for (let key = 0; key < arr.length; key++) {
newArr.push(permute(arr[key]))
}
return newArr
}
// 所有组合方式
function permute(input) {
var permArr = [],
usedChars = [];
function main(input) {
var i, ch;
for (i = 0; i < input.length; i++) {
ch = input.splice(i, 1)[0];
usedChars.push(ch);
if (input.length == 0) {
permArr.push(usedChars.slice());
}
main(input);
input.splice(i, 0, ch);
usedChars.pop();
}
return permArr
}
return main(input);
};
function arrayN(arr, start, count, Num, res, stack) {
//用递归实现,把N个循环用同一个循环实现
for (let i = start; i < arr.length - count + 1; i++) {
//记录索引号
res[count - 1] = i;
if (count - 1 == 0) {
let oneResult = []
for (let j = Num - 1; j >= 0; j--) {
oneResult.push(arr[res[j]])
}
stack.push(oneResult)
} else {
arrayN(arr, i + 1, count - 1, Num, res, stack)
}
}
}
}
listenAjax(exerciseList, ans);
}
// 学习内容页面
function studycontentList() {
showEl.innerText = "加载学习列表中";
const learArr = [];
let isLearArr = [];
function reqs(data) {
data.res.data.course_chapter.forEach(el => {
if (!el.is_locked) {
el.section_leaf_list.forEach(el_ => {
if (el_.leaf_list) {
learArr.push(...el_.leaf_list)
} else {
learArr.push(el_)
}
})
}
});
showEl.innerText = "获取到列表,查询进度";
}
function prss(data) {
const prssArr = data.res.data.leaf_schedules
// 未完成列表
const dontLear = learArr.filter(el => {
const keys = Object.keys(prssArr)
const findData = keys.find(i => +i === el.id);
if (findData !== undefined) {
const learData = prssArr[findData]
if (learData !== undefined) {
if (typeof learData === "number") {
if (+learData !== 1) {
return true;
}
} else if (learData.total !== learData.done) {
return true;
}
} else {
return true;
}
} else {
return true;
}
})
console.log(dontLear)
// 作业
const work = dontLear.filter(el => el.leaf_type == 6);
// 视频
const video = dontLear.filter(el => el.leaf_type != 6);
showEl.innerText = `挂机执行中,剩余(${dontLear.length})...`;
if (work.length) {
startLears([work[0], ...video.splice(0, videoNum)])
} else {
startLears([...video.splice(0, videoNum)])
}
}
function startLears(arr) {
if (!arr.length) {
showEl.innerText = "恭喜,所有课程均已完成!";
}
console.log("开始学习这些课程", arr)
const app = document.querySelector('[data-v-3d8fef40]').__vue__;
const learEl = document.querySelectorAll(".learing-iframe-box .boxs")
arr.forEach(el => {
if (isLearArr.find(i => i.id === el.id) === undefined) {
const div = document.createElement("div");
div.classList.add("boxs");
div.setAttribute("lear-id", el.id)
div.setAttribute("pr-name", el.name)
const tempIFrame = document.createElement("iframe");
div.appendChild(tempIFrame)
if (el.leaf_type === 6) {
tempIFrame.src = `https://uestcedu.yuketang.cn/pro/lms/${app.$data.sign}/${app.$data.classroom_id}/homework/${el.id}`
} else {
tempIFrame.src = `https://uestcedu.yuketang.cn/pro/lms/${app.$data.sign}/${app.$data.classroom_id}/video/${el.id}`
}
tempIFrame.classList.add("learing-iframe");
document.querySelector(".learing-iframe-box").appendChild(div);
isLearArr.push(el.id);
} else { }
})
isLearArr = arr;
learEl.forEach(el => {
const find = isLearArr.find(i => i.id === +el.getAttribute("lear-id"))
if (find === undefined) {
console.log(el.getAttribute("pr-name"), "此课已学习完成")
el.remove();
}
})
}
listenAjax(studyList, reqs);
listenAjax(progress, prss);
// 循环
function loop() {
setTimeout(() => {
console.log("更新列表")
document.querySelector('[data-v-3d8fef40]').__vue__.getLearnSchedule();
const learEl = document.querySelectorAll(".learing-iframe-box .boxs")
if (learEl.length) {
loop()
}
}, 10000);
}
loop()
}
// 视频播放
function playVideo() {
showEl.innerText = `视频播放挂机页面 [等待捕获视频url]`;
const tims = setTimeout(() => {
showEl.innerText = `页面加载错误,正在重载页面`;
setTimeout(() => {
location.reload()
}, 3000)
}, 60000)
function startListen(data) {
clearTimeout(tims)
console.log("获取到视频链接", data.res)
let lastTime = null,
reloadCount = 30,
thisCount = 0
showEl.innerText = `视频播放挂机页面 [等待创建video节点]`;
function loop() {
const video = document.querySelector("video")
if (video) {
video.muted = true;
video.playbackRate = 2;
video.play()
const dur = parseInt(video.duration),
curr = parseInt(video.currentTime)
showEl.innerText = `视频播放中 [${curr}/${dur}]`;
if (lastTime === curr) {
thisCount++;
} else {
thisCount = 0;
}
lastTime = curr;
if (reloadCount === thisCount) {
showEl.innerText = `视频长时间未播放,正在重载页面`;
setTimeout(() => {
location.reload()
}, 3000)
return;
}
}
setTimeout(loop, 2000)
}
loop();
}
let ones = true;
listenAjax(videoPress, (data) => {
console.log("捕获到更新时长请求", data);
let urls = data.url
let defaultHearder = data.header
let heart_data = data.send.heart_data.reverse()[0]
const newList = []
console.log(heart_data)
let leng = parseInt(heart_data.d / 10)
for (let i = 0; i < leng; i++) {
let newObj = JSON.parse(JSON.stringify(heart_data))
if (i + 1 < leng) {
newObj.cp = heart_data.tp + parseInt((heart_data.d - heart_data.tp) / leng * i)
} else {
newObj.cp = heart_data.d
}
newObj.et = 'play'
newList.push(newObj)
}
console.log("新构建结构", newList)
showEl.innerText = `构建虚拟学习进度`;
let postData = {
heart_data: newList
}
if (ones) {
ones = false
setTimeout(() => {
axios.post(urls, postData, defaultHearder).then(res => {
showEl.innerText = `请求结束,等待服务器更新`;
console.log("模拟请求返回", res)
setTimeout(() => {
document.querySelector(".log-detail").click()
showEl.innerText = `重载页面`;
setTimeout(() => {
location.reload()
}, 30000)
}, 3000)
})
}, 3000)
} else {
console.log("已经提交过了")
}
})
listenAjax(getVideoUrl, startListen);
}
// ajax监听
// function listenAjax(rule, callback) {
// // 监听所有请求
// const originOpen = XMLHttpRequest.prototype.open;
// const originSend = XMLHttpRequest.prototype.send;
// // 重写open
// XMLHttpRequest.prototype.open = function () {
// this.addEventListener('load', function (obj) {
// const url = obj.target.responseURL; // obj.target -> this
// if (rule.test(url)) {
// callback(JSON.parse(this.response))
// }
// });
// originOpen.apply(this, arguments);
// };
// // 重写send
// XMLHttpRequest.prototype.send = function () {
// originSend.apply(this, arguments);
// };
// }
// 监听所有请求
const originOpen = XMLHttpRequest.prototype.open;
const originSend = XMLHttpRequest.prototype.send;
const originHeader = XMLHttpRequest.prototype.setRequestHeader
// 重写open
XMLHttpRequest.prototype.open = function () {
this.addEventListener('load', function (obj) {
const url = obj.target.responseURL; // obj.target -> this
listenList.forEach(el => {
if (el.rule.test(url)) {
const find = ajaxList.find(el => el.xml === this)
if (find) {
find.url = url
find.res = JSON.parse(this.response)
el.callback(find)
} else {
el.callback(false)
}
}
})
});
originOpen.apply(this, arguments);
};
// 重写send
XMLHttpRequest.prototype.send = function () {
const xml = ajaxList.find(el => el.xml === this)
if (xml) {
xml.send = JSON.parse(arguments[0])
}
originSend.apply(this, arguments);
};
// 重写setRequestHeader
XMLHttpRequest.prototype.setRequestHeader = function () {
const xml = ajaxList.find(el => el.xml === this)
if (xml) {
xml.header[arguments[0]] = arguments[1]
} else {
ajaxList.push({
xml: this,
url: "",
header: {
[arguments[0]]: arguments[1]
}
})
}
originHeader.apply(this, arguments)
}
function listenAjax(rule, callback) {
listenList.push({
rule,
callback
})
}
})();