国资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/[email protected]/dist/sm2.min.js
// @require      https://fastly.jsdelivr.net/npm/[email protected]/dist/sm2.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/sm3.min.js
// @require      https://fastly.jsdelivr.net/npm/[email protected]/dist/sm3.min.js
// @resource     https://cdn.staticfile.org/limonte-sweetalert2/11.7.1/sweetalert2.min.css
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/sweetalert2.all.min.js
// @require      https://fastly.jsdelivr.net/npm/[email protected]/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>`
                })
            }
        })
    }
})();