// ==UserScript==
// @name 国资e学刷课程,可秒刷
// @namespace https://greasyfork.org/zh-CN/scripts/493533/feedback
// @version 2.0
// @description 2.0版本更新!现已全面支持最新版国资e学平台,点击“即刻开刷”按钮,即可自动完成播放页面内所有课程。UI交互优化升级,体验更加丰富流畅!本脚完全免费,提醒大家谨防二次售卖,确保使用安全无忧。
// @author ZouYS
// @match https://elearning.tcsasac.com/*
// @icon https://p3-sign.douyinpic.com/obj/douyin-user-image-file/1ebae6a7405da95fb2633a3512294cb1?x-expires=1731610800&x-signature=fI7E3NVqdhaBvIuFFQ9afCdJXsk%3D&from=2064092626&s=sticker_comment&se=false&sc=sticker_heif&biz_tag=aweme_comment&l=202411142138312F77099E145987055E9E
// @require https://cdn.jsdelivr.net/npm/sm-crypto@0.3.13/dist/sm2.min.js
// @require https://fastly.jsdelivr.net/npm/sm-crypto@0.3.13/dist/sm2.min.js
// @require https://cdn.jsdelivr.net/npm/sm-crypto@0.3.13/dist/sm3.min.js
// @require https://fastly.jsdelivr.net/npm/sm-crypto@0.3.13/dist/sm3.min.js
// @resource https://cdn.staticfile.org/limonte-sweetalert2/11.7.1/sweetalert2.min.css
// @require https://cdn.jsdelivr.net/npm/sweetalert2@11.12.2/dist/sweetalert2.all.min.js
// @require https://fastly.jsdelivr.net/npm/sweetalert2@11.12.2/dist/sweetalert2.all.min.js
// @run-at document-start
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// @license Apache-2.0
// ==/UserScript==
// 2222 4615502 22220 22223 2666 33233 22266 11222
(function () {
'use strict';
let requestObject = {
updateUserVideoTime: {
url: 'https://elearning.tcsasac.com/prod-api/study/study/updateUserVideoTime',
method: 'POST',
data: {
"fileId": "",
"videoTime": 0,
"viewDuration": 0,
"courseId": 0
},
res: {
code: 200,
msg: "操作成功",
}
},
asynUpdateCourseSchedule: {
url: 'https://elearning.tcsasac.com/prod-api/study/study/asynUpdateCourseSchedule',
method: 'GET',
data: {
courseId: 0,
}
},
addUserVideoLog: {
url: 'https://elearning.tcsasac.com/prod-api/study/study/addUserVideoLog',
method: 'POST',
data: {}
},
addUserCourseLog: {
url: 'https://elearning.tcsasac.com/prod-api/study/study/addUserCourseLog',
method: 'POST',
data: {}
},
/**
* 获取课程总时间
*/
getUserVideoLogListByCourseId: {
url: 'https://elearning.tcsasac.com/prod-api/study/userVideo/getUserVideoLogListByCourseId',
method: 'GET',
data: {
courseId: 0
}
}
}
/**
* 公钥
* @type {string}
*/
const transferSM2Public = '04dc384b738d4261dcaf2e042f68cd037d3536df5286cc2059bc20a0a81f1c42abf85b7fd7f4eb5ae2f3a8476297ffdcc53d64d9551d5a7da8a761a871c897728d'
const transferSM2Private = "7ea7cec7043b1eb7a8c4110bb643725216cb2dc1d26dfdb2acffc1e486ba8483"
const backSM2Public = "04a90d1f589d45e9d7dd45e98c6ef86742354619d846414ecff18557f20f65763d2e95e3c0e20e3d7d8601abfa289aa63ea0c8a71647c99668f5b19a7abb8cc6c2"
const backSM2Private = '9ffd52bc4e2cd0464ba19be8215531eaab0b56e276a6f8208c68e30917e7a5a'
/**
* sm加密
* @param data payload
* @returns {string} 密文
*/
const encrypt = (data) => {
let n = Date.now();
const o = sm3("".concat(data, "lifeismovie").concat(n))
, a = sm2.doEncrypt("".concat(data, "lifeismovie").concat(n, "heykong").concat(o), transferSM2Public);
return "04".concat(a, "0g5z2w5j_whatsup")
}
/**
* 解密
* @param secret 密文
* @param t
* @param n
* @returns {object | null}
*/
const decrypt = (secret, t = transferSM2Private, n = "request") => {
let o = null;
const a = secret.match(/^04(\S*)/);
if (a) {
const e = "request" === n ? a[1].match(/(\S*)8l6z9c3j_whatsup$/) : a[1].match(/(\S*)0g5z2w5j_whatsup$/);
if (e) {
const a = sm2.doDecrypt(e[1], t)
, r = a.split("heykong")[0]
, l = a.split("heykong")[1];
if (r && l) {
const e = r.split("lifeismovie")[1]
, t = r.split("lifeismovie")[0];
// console.log('t:', t)
if (sm3(r) === l) {
o = JSON.parse(t)
} else
o = null
} else
o = null
} else
o = null
} else
o = null;
return o
}
/**
* 获取当前课程ID
* @returns {number|null}
*/
const initCourseInfo = () => {
const curUrl = location.href.split('eparams=')[1];
if (!curUrl) {
console.error('cant get courseId!')
return null
}
let obj = decrypt(curUrl, undefined, "other");
console.log("init obj", obj)
return obj.id
}
/**
* 获取当前课程信息
* @param courseId
* @returns {Promise<object>}
*/
const getCourseInfo = async (courseId) => {
let obj = {...requestObject.getUserVideoLogListByCourseId}
obj.data.courseId = courseId
const res = await request(obj.url, "GET", obj.data);
if (res) {
return res
} else {
console.error('getCourseInfo error:', courseId)
}
}
/**
* 更新视频观看时长
* @param courseId
* @param fileId
* @param videoTime
* @returns {Promise<object|null>}
*/
const updateCourseTime = async (courseId, fileId, videoTime) => {
let obj = {...requestObject.updateUserVideoTime}
obj.data.courseId = courseId;
obj.data.fileId = fileId;
obj.data.videoTime = videoTime;
obj.data.viewDuration = videoTime - 1;
try {
return await xhrRequest(obj.url, "POST", obj.data);
} catch (error) {
console.error('updateCourseTime error:', error);
return null;
}
}
/**
* 同步更新课程表
* @param courseId
* @returns {Promise<void>}
*/
const updateCourseSchedule = async (courseId) => {
let obj = {...requestObject.asynUpdateCourseSchedule}
obj.data.courseId = courseId
const res = await request(obj.url, "GET", obj.data)
}
/**
* fetch请求
* @param url
* @param method
* @param data
* @returns {Promise<void>}
*/
const request = async (url, method = "GET", data) => {
url = method === "GET" ? (url + '?edata=' + encrypt(new URLSearchParams(data).toString())) : url
let res = await fetch(url, {
"headers": {
"accept": "application/json, text/plain, */*",
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
"authorization": `Bearer ${document.cookie.split('user-Token=')[1].split(';')[0]}`,
"cache-control": "no-cache",
"pragma": "no-cache",
"sec-ch-ua": "\"Chromium\";v=\"130\", \"Microsoft Edge\";v=\"130\", \"Not?A_Brand\";v=\"99\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin"
},
"referrer": `${location.href}`,
"referrerPolicy": "strict-origin-when-cross-origin",
"body": method === "GET" ? null : (JSON.stringify({
edata: encrypt(JSON.stringify(data))
})),
"method": method,
"mode": "cors",
"credentials": "include"
});
if (res.ok) {
res = await res.text()
res = (decrypt(res, backSM2Private))
console.log('url', url)
console.log(res)
return res
}
return null
}
/**
* xhr请求,同步时间使用
* @param url
* @param method
* @param data
* @returns {Promise<void>}
*/
const xhrRequest = (url, method = "GET", data) => {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open(method, url, true);
xhr.setRequestHeader("Accept", "application/json, text/plain, */*");
xhr.setRequestHeader("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6");
xhr.setRequestHeader("Authorization", `Bearer ${document.cookie.split('user-Token=')[1].split(';')[0]}`);
xhr.setRequestHeader("Cache-Control", "no-cache");
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8;");
xhr.setRequestHeader("Pragma", "no-cache");
xhr.referrer = `${location.href}`
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
try {
let res = decrypt(xhr.responseText, backSM2Private);
if (res) {
console.log(`${url} ->`, res);
resolve(res);
} else {
console.error("Decrypt failed:", res);
reject("Decryption failed");
}
} catch (e) {
console.error("Error in decryption:", e);
reject(e);
}
} else {
console.error(`Request failed with status ${xhr.status}:`, xhr.responseText);
reject(xhr.responseText);
}
}
};
let body = JSON.stringify({
"edata": encrypt(JSON.stringify(data))
});
xhr.send(body);
})
}
/**
* 主模块
* @param courseId
* @returns {Promise<void>}
*/
const run = async (courseId) => {
try {
const courseInfo = await getCourseInfo(courseId);
if (Array.isArray(courseInfo.data.chapterList[0].fileInfoList)) {
for (const file of courseInfo.data.chapterList[0].fileInfoList) {
if (file.viewDuration <= file.videoTime) {
console.log(file.fileId)
const res = await updateCourseTime(courseId, file.fileId, file.videoTime);
if (res.code === 200) {
console.log('updateCourseTime: ' + res.msg + '--' + file.fileName)
await updateCourseSchedule(courseId)
} else {
console.error('updateCourseTime error:', res)
}
}
}
}
if(Swal){
Swal.fire({
title: "刷课成功!",
text: `当前课程列表已全部完成!点击确定立即刷新页面,显示最新结果!`,
icon: 'success',
showCancelButton: true,
confirmButtonColor: "#FF4DAFFF",
cancelButtonText: "取消,等会刷新",
confirmButtonText: "确定,立即刷新",
}).then((result) => {
if (result.isConfirmed) {
location.reload();
}
});
}
console.log("操作成功!!!!");
}catch (e) {
console.error(e);
if(Swal){
Swal.fire({
title: "错误!",
text: e.toString()+"请在视频播放页面使用!!!",
icon: 'error',
confirmButtonColor: "#eb00fd",
confirmButtonText: "确定",
})
}
}
}
//样式
let style = `.button-3 {
appearance: none;
background-color: #e52b13;
border: 1px solid rgba(27, 31, 35, .15);
border-radius: 6px;
box-shadow: rgba(27, 31, 35, .1) 0 1px 0;
box-sizing: border-box;
color: #ffffff;
cursor: pointer;
display: inline-block;
font-family: -apple-system,system-ui,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";
font-size: 14px;
font-weight: 600;
line-height: 20px;
padding: 6px 16px;
position: absolute;
left: 20px;
top: 300px;
text-align: center;
text-decoration: none;
user-select: none;
-webkit-user-select: none;
touch-action: manipulation;
vertical-align: middle;
white-space: nowrap;
}
.button-3:focus:not(:focus-visible):not(.focus-visible) {
box-shadow: none;
outline: none;
}
.button-3:hover {
background-color: #2c974b;
}
.button-3:focus {
box-shadow: rgba(46, 164, 79, .4) 0 0 0 3px;
outline: none;
}
.button-3:disabled {
background-color: #94d3a2;
border-color: rgba(27, 31, 35, .1);
color: rgba(255, 255, 255, .8);
cursor: default;
}
.button-3:active {
background-color: #298e46;
box-shadow: rgba(20, 70, 32, .2) 0 1px 0 inset;
}`
window.onload = () => {
let myStyle = document.createElement('style')
myStyle.innerHTML = style;
document.head.appendChild(myStyle);
/*let intercept=GM_GetValue*/
let div = document.createElement('div');
div.innerHTML = `<div style="left: 0;top: 300px;" id="my1" class="button-3" >即刻开刷</div>
<div style="left: 0;top: 340px;" id="my2" class="button-3" >2222</div>`
document.body.appendChild(div);
let isClick = false;
let my1 = document.getElementById('my1')
let my2 = document.getElementById('my2')
my1.addEventListener("click", () => {
isClick = !isClick;
if (isClick) {
const courseId = initCourseInfo()
my1.innerText = "刷课中"
run(courseId)
my1.innerText = "点击开刷"
} else {
my1.innerText = "点击开刷"
}
})
my2.addEventListener("click", () => {
if(Swal){
Swal.fire({
title: "彩蛋OvO",
icon: 'info',
confirmButtonColor: "rgb(253,0,135)",
confirmButtonText: "感谢~",
html: `<a href="https://www.douyu.com/2222" target="_blank" style="color: #f2c7d9; text-decoration: underline; text-decoration-color: #f2c7d9;"><span style="color: #f2c7d9;">2222</span></a><br>小小彩蛋,制作不易!<br>为保证每个人都能有更好的体验,恳请大家合理使用哦。<br>若您觉得脚本还不错,不妨留下您的 <a href="https://greasyfork.org/zh-CN/scripts/493533/feedback" target="_blank" style="color: #FF5733; text-decoration: underline; text-decoration-color: #FF5733;"<span style="color: #FF5733;">好评与鼓励</span></a>。<br>您的每一份支持,都是我不断前行的动力!🧡💪<br>感谢您的理解与支持!😊✨<br><a target="_blank" href="https://greasyfork.org/zh-CN/scripts/493533/feedback">点击前往评论</a>`
})
}
})
}
})();