国资e学刷课程,可秒刷

2.0版本更新!现已全面支持最新版国资e学平台,点击“即刻开刷”按钮,即可自动完成播放页面内所有课程。UI交互优化升级,体验更加丰富流畅!本脚完全免费,提醒大家谨防二次售卖,确保使用安全无忧。

// ==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>`
                })
            }
        })
    }
})();