Greasy Fork

上海大学网站增强 - 刷课助手

<选课系统>1.第二、三轮选课自助刷课,解放双手。【人人有课刷,抵制卖课狗】【支持多门课程同时刷】 2.选课学期自动选择 3.选课排名页面标红排名超过额定人数的课程 4.学分完成情况页面,原始成绩换算绩点,选课学期可标红 5.所有存在显示课程号的页面,支持点击课程号查看课程介绍,右键直接复制课程号 <教务管理>1.教学评估页面可一键赋值,支持全部赋值和单行赋值 2.成绩查询页面在成绩未发布时自动刷新 3.移除主页企业微X广告

// ==UserScript==
// @name         上海大学网站增强 - 刷课助手
// @namespace    https://github.com/panghaibin/shu-web-js
// @version      3.5.2
// @description  <选课系统>1.第二、三轮选课自助刷课,解放双手。【人人有课刷,抵制卖课狗】【支持多门课程同时刷】 2.选课学期自动选择 3.选课排名页面标红排名超过额定人数的课程 4.学分完成情况页面,原始成绩换算绩点,选课学期可标红 5.所有存在显示课程号的页面,支持点击课程号查看课程介绍,右键直接复制课程号 <教务管理>1.教学评估页面可一键赋值,支持全部赋值和单行赋值 2.成绩查询页面在成绩未发布时自动刷新 3.移除主页企业微X广告
// @author       panghaibin
// @match        *://xk.autoisp.shu.edu.cn/*
// @match        *://cj.shu.edu.cn/*
// @grant        none
// @license      MIT
// @supportURL   https://github.com/panghaibin/shu-web-js/issues
// ==/UserScript==

(function () {
    'use strict';

    //--------刷课配置 开始--------
    // 是否开启刷课 (true: 开启 false: 关闭)
    let START_SELECT = false;

    // 课程设置,可设置多个
    let COURSE_SETTING = [
        // 格式: ['课程号', '教师号'],
        // 以下两个为示例 记得修改或删除,也可增加更多
        ['0085X078', '1091'],
        ['0085X078', '1331'],
    ];

    // 刷课间隔,默认 8000 ,单位毫秒 一般不需要修改
    let SELECT_DELAY = 8000
    //--------刷课配置 结束--------

    if (location.host === 'xk.autoisp.shu.edu.cn') {
        if (location.pathname === '/CourseSelectionStudent/PlanQuery') {
            plan_query_page();
        } else if (location.pathname === '/StudentQuery/QueryEnrollRank') {
            rclick_copy_course_id();
            red_rank();
        } else if (location.pathname === '/CourseSelectionStudent/FuzzyQuery' && START_SELECT) {
            select_course_helper();
        } else if (location.pathname === '/Home/TermIndex' && location.search !== '?auto_select=0') {
            auto_select_term();
        } else if (location.pathname === '/StudentQuery/QueryCourse'
            || location.pathname === '/StudentQuery/QueryCourseTable'
            || location.pathname === '/StudentQuery/QueryDeleteCourse'
        ) {
            rclick_copy_course_id();
        }
        if (location.pathname !== '/Home/TermIndex') {
            manual_select_term();
            add_xk_footer();
        }
    } else if (location.host === 'cj.shu.edu.cn') {
        if (location.pathname === '/StudentPortal/Evaluate') {
            evaluate_helper();
        } else if (location.pathname === '/Home/StudentIndex') {
            remove_cj_ad();
        } else if (location.pathname === '/StudentPortal/ScoreQuery') {
            auto_fresh_score();
        }
    }

    function score_conversion(item) {
        let score_num = item.innerText * 1;
        let score_raw = item.innerText;
        if (score_raw.length > 0) {
            if (score_num >= 90 && score_num <= 100 || score_raw === "A") {
                item.innerText = score_raw + ' / 4.0';
            } else if (score_num >= 85 && score_num <= 89.9 || score_raw === "A-") {
                item.innerText = score_raw + ' / 3.7';
            } else if (score_num >= 82 && score_num <= 84.9 || score_raw === "B+") {
                item.innerText = score_raw + ' / 3.3';
            } else if (score_num >= 78 && score_num <= 81.9 || score_raw === "B") {
                item.innerText = score_raw + ' / 3.0';
            } else if (score_num >= 75 && score_num <= 77.9 || score_raw === "B-") {
                item.innerText = score_raw + ' / 2.7';
            } else if (score_num >= 72 && score_num <= 74.9 || score_raw === "C+") {
                item.innerText = score_raw + ' / 2.3';
            } else if (score_num >= 68 && score_num <= 71.9 || score_raw === "C") {
                item.innerText = score_raw + ' / 2.0';
            } else if (score_num >= 66 && score_num <= 67.9 || score_raw === "C-") {
                item.innerText = score_raw + ' / 1.7';
            } else if (score_num >= 64 && score_num <= 65.9 || score_raw === "D") {
                item.innerText = score_raw + ' / 1.5';
            } else if (score_num >= 60 && score_num <= 63.9 || score_raw === "D-") {
                item.innerText = score_raw + ' / 1.0';
            } else if ((score_num >= 0 && score_num <= 59.9 || score_raw === "F")) {
                item.innerText = score_raw + ' / 0.0';
            } else if (score_raw === "P" || score_raw === "L") {
                item.innerText = score_raw + ' / 不计';
            }
            // 标红压线绩点 暂时不使用
            // if (score_num === 89 || score_num === 84 || score_num === 81 || score_num === 77 || score_num === 74 ||
            //     score_num === 71 || score_num === 67 || score_num === 65 || score_num === 63 || score_num === 59) {
            //     item.style.color = 'red';
            // }
        }
    }

    function red_rank() {
        let elements;
        elements = document.getElementsByName('rowclass');
        for (let i = 0; i < elements.length; ++i) {
            let row = elements[i];
            let room = row.children[4].innerText * 1;
            let _rank = row.children[6].innerText.split('-');
            let rank = _rank[_rank.length - 1] * 1;
            if (rank > room) {
                for (let j = 0; j < row.children.length; ++j) {
                    elements[i].children[j].style.color = 'red';
                    elements[i].children[j].style.fontWeight = 'bold';
                }
            }
        }
    }

    function select_course_helper() {
        let i = 0;
        let select_id = setInterval(function () {
            const index = i % COURSE_SETTING.length;
            const course = COURSE_SETTING[index]
            const course_id = course[0];
            const teacher_id = course[1];
            document.getElementsByName('CID')[0].defaultValue = course_id;
            document.getElementsByName('TeachNo')[0].defaultValue = teacher_id;
            document.getElementById('QueryAction').click();
            setTimeout(function () {
                let tbllist = document.getElementsByClassName('tbllist');
                let room = tbllist[0].children[0].children[1].children[9].innerText * 1;
                let num = tbllist[0].children[0].children[1].children[10].innerText * 1;
                console.log('第' + ++i + '次 课程号' + course_id + ' 教师号' + teacher_id + ' 容量' + room + ' 人数' + num);
                if (room > num) {
                    tbllist[0].children[0].children[1].children[0].children[0].checked = true;
                    document.getElementById('CourseCheckAction').click();
                    let across_campus = document.getElementById('NotCollegeAction');
                    if (across_campus != null) {
                        setTimeout(function () {
                            across_campus.click();
                        }, 1000);
                    }
                    tbllist = document.getElementsByClassName('tbllist');
                    let callback_msg = tbllist[1].children[0].children[1].children[5].innerText
                    if (callback_msg.includes('成功')) {
                        COURSE_SETTING.splice(index, 1);
                        if (COURSE_SETTING.length === 0) {
                            clearInterval(select_id);
                        } else {
                            CloseDialog('divOperationResult');
                        }
                    } else {
                        // 关闭选课失败的窗口
                        CloseDialog('divOperationResult');
                    }
                    console.log(callback_msg);
                }
            }, 1000);
        }, SELECT_DELAY);
    }

    function evaluate_helper() {
        let tbllist = document.getElementsByClassName('tbllist');
        let th = document.createElement('TH');
        th.innerText = '一键评估';
        let heading = tbllist[0].children[0].children[0];
        heading.insertBefore(th, heading.children[4]);

        let evaluate_list = ['结果', '非常大', '比较大', '一般', '很少'];
        let course_count = tbllist[0].children[0].childElementCount - 2;
        for (let i = 1; i <= course_count; ++i) {
            let select = document.createElement('SELECT');
            select.style.backgroundColor = '#ffc';
            for (let j = 0; j < evaluate_list.length; j++) {
                let option = document.createElement('OPTION');
                option.innerText = evaluate_list[j];
                option.value = j;
                if (j === 0) {
                    option.innerText = '单行' + option.innerText;
                    option.selected = true;
                }
                select.appendChild(option);
            }
            select._params = {'row': i - 1};
            select.addEventListener('change', function () {
                let row = event.target._params['row']
                for (let j = 0; j < 4; ++j) {
                    let evaluate_name = 'classlist[' + row + '].ItemValue[' + j + ']';
                    let raw_select = document.getElementsByName(evaluate_name);
                    raw_select[0].children[this.value].selected = true;
                }
            }, false);
            let td = document.createElement('TD');
            td.appendChild(select);
            let cource = tbllist[0].children[0].children[i];
            cource.insertBefore(td, cource.children[4]);
        }

        let all_select = document.createElement('SELECT');
        all_select.style.backgroundColor = '#ffc';
        all_select.style.marginRight = '15px';
        for (let i = 0; i < evaluate_list.length; i++) {
            let option = document.createElement('OPTION');
            option.innerText = evaluate_list[i];
            option.value = i;
            if (i === 0) {
                option.innerText = '全部' + option.innerText;
                option.selected = true;
            }
            all_select.appendChild(option);
        }
        all_select.addEventListener('change', function () {
            let selects = document.getElementsByTagName('SELECT');
            for (let i = 0; i < selects.length; ++i) {
                selects[i].children[this.value].selected = true;
            }
        }, false);
        let row = tbllist[0].children[0].children;
        let last_row = row[row.length - 1].children[0].children[0];
        let save_btn = last_row.children[0];
        last_row.insertBefore(all_select, save_btn);
    }

    function remove_cj_ad() {
        document.getElementsByClassName('div_master_content')[0].remove();
    }

    function auto_select_term() {
        let term_list = document.getElementsByName('rowterm');
        document.getElementById('termId').value = term_list[term_list.length - 1].getAttribute('value');
        document.getElementsByTagName('button')[0].click();
    }

    function manual_select_term() {
        let menu = document.querySelector('.term-menu');
        if (menu !== null) {
            let a_tag = menu.getElementsByTagName('a')[0];
            a_tag.href = a_tag.href + '?auto_select=0';
        }
    }

    function add_xk_footer() {
        setInterval(() => {
            let footer = document.getElementsByClassName('main-footer')[0];
            if (typeof (footer) !== "undefined") {
                let footer_div = footer.children[1];
                if (!footer_div.innerHTML.includes('刷课助手')) {
                    let skzs = document.createElement('A');
                    skzs.innerText = '刷课助手已加载';
                    skzs.href = 'https://greasyfork.org/zh-CN/scripts/434613';
                    skzs.target = '_blank';
                    footer_div.innerHTML = footer_div.innerHTML + ' - ' + skzs.outerHTML;
                }
            }
        }, 0)
    }

    function get_current_grade_term() {
        let term_name = document.getElementsByClassName('dropdownterm')[0].children[0].children[1].innerText;
        let term_year = /(\d{2})学年/.exec(term_name)[1] * 1;
        let term_type = /(.)季学期/.exec(term_name)[1];
        let id_num = document.getElementsByClassName('nav-function-text')[0].innerText;
        let enroll_year = /(\d{2})\d{6}/.exec(id_num)[1] * 1;
        let term_list = {'秋': 1, '冬': 2, '春': 3, '夏': 4};
        let term_num = term_list[term_type];
        let grade_num = term_year - enroll_year;
        return {'class': 'gt-' + grade_num + term_num, 'grade': grade_num, 'term': term_num};
    }

    function onchange_grade_term_select() {
        let all_gt = document.getElementsByClassName('gt');
        for (let i = 0; i < all_gt.length; i++) {
            all_gt[i].style.color = '';
        }
        let grade_choice = document.getElementsByClassName('grade-select')[0].value;
        let term_choice = document.getElementsByClassName('term-select')[0].value;
        let choice_gt = document.getElementsByClassName('gt-' + grade_choice + term_choice);
        for (let i = 0; i < choice_gt.length; i++) {
            choice_gt[i].style.color = 'red';
        }
    }

    function plan_query_page() {
        let grade_list = {'一': 1, '二': 2, '三': 3, '四': 4, '五': 5};
        let term_list = {'秋': 1, '冬': 2, '春': 3, '夏': 4};
        let td_list = document.getElementsByTagName('TD');
        for (let i = 0; i < td_list.length; i++) {
            if (td_list[i].innerText.includes('学期')) {
                td_list[i].innerHTML = td_list[i].innerHTML.replaceAll(/(.)年级(.)季学期/g,
                    function ($0, $1, $2) {
                        let span = document.createElement('SPAN');
                        span.className = 'gt gt-' + grade_list[$1] + '0 gt-' + grade_list[$1] + term_list[$2];
                        span.innerText = $0;
                        return span.outerHTML;
                    });
            } else if (is_cource_id_td(td_list[i])) {
                edit_cource_id_td(td_list[i]);
            } else if (typeof (td_list[i].attributes.name) !== "undefined"
                && ['tdscorecnotpass', 'tdscore', 'tdscorecpass'].includes(td_list[i].attributes.name.value)) {
                score_conversion(td_list[i]);
            }
        }

        let reverse_grade_list = {1: '一', 2: '二', 3: '三', 4: '四', 5: '五'};
        let reverse_term_list = {1: '秋季', 2: '冬季', 3: '春季', 4: '夏季', 0: '全部'};
        let current_grade_term = get_current_grade_term();

        let grade_select = document.createElement('SELECT');
        grade_select.className = 'grade-select';
        grade_select.style.backgroundColor = '#ffc';
        grade_select.addEventListener('change', onchange_grade_term_select);
        let grade_val = current_grade_term['grade'];
        for (let i = 1; i <= 5; i++) {
            let option = document.createElement('OPTION');
            option.innerText = reverse_grade_list[i] + '年级';
            option.value = i;
            if (grade_val === i) {
                option.selected = true;
            }
            grade_select.appendChild(option);
        }

        let term_select = document.createElement('SELECT');
        term_select.className = 'term-select';
        term_select.style.backgroundColor = '#ffc';
        term_select.addEventListener('change', onchange_grade_term_select);
        let term_val = current_grade_term['term'];
        for (let i = 0; i <= 4; i++) {
            let option = document.createElement('OPTION');
            option.innerText = reverse_term_list[i] + '学期';
            option.value = i;
            if (term_val === i) {
                option.selected = true;
            }
            term_select.appendChild(option);
        }

        let th_list = document.getElementsByTagName('TH');
        for (let i = 0; i < th_list.length; i++) {
            if (th_list[i].innerText.includes('教学计划')) {
                th_list[i].appendChild(grade_select);
                th_list[i].appendChild(term_select);
                break;
            }
        }

        let gt_class = document.getElementsByClassName(current_grade_term['class']);
        for (let i = 0; i < gt_class.length; i++) {
            gt_class[i].style.color = 'red';
        }
    }

    function fresh_score(interval_id) {
        let score_table = document.getElementById('divList');
        if (score_table.innerText.includes('学号') && !score_table.innerText.includes('未提交')) {
            if (interval_id !== null) {
                let title = '成绩已发布';
                blink_title(title);
                clearInterval(interval_id);
            }
            return false;
        } else if (score_table.innerText.includes('评估')) {
            let title = '成绩已发布,教学评估完成后可查看';
            blink_title(title);
            clearInterval(interval_id);
            return false;
        } else {
            score_table.innerHTML = score_table.innerHTML + '<br>8秒后自动刷新';
            return true;
        }
    }

    function auto_fresh_score() {
        let interval_id = null;
        if (fresh_score(interval_id)) {
            let i = 0;
            interval_id = setInterval(function () {
                document.title = '已刷新' + ++i + '次成绩';
                document.getElementsByTagName('INPUT')[0].click();
                setTimeout(fresh_score, 2000, interval_id);
            }, 8000);
        }
    }

    function rclick_copy_course_id() {
        setInterval(function () {
            let td_list = document.getElementsByTagName('TD');
            for (let i = 0; i < td_list.length; i++) {
                if (is_cource_id_td(td_list[i])) {
                    edit_cource_id_td(td_list[i]);
                }
            }
        }, 500);
    }

    function is_cource_id_td(item) {
        return item.innerText.length === 8 && /\d.{6}\d/.test(item.innerText);
    }

    function edit_cource_id_td(item) {
        let course_id = item.innerText;
        item.className = 'course-id';
        item.style.cursor = 'pointer';
        item.title = '点击查看课程简介,右键复制课程号';
        item.oncontextmenu = function (e) {
            e.preventDefault();
            copy_to_clipboard(course_id)
                .then(() => toast_msg(item, '已复制'))
                .catch(() => toast_msg(item, '复制失败'));
        }
        item.onclick = function () {
            window.open("../DataQuery/QueryCourseIntro?courseNo=" + course_id);
        }
    }

    function copy_to_clipboard(text) {
        // From https://stackoverflow.com/a/65996386
        if (navigator.clipboard && window.isSecureContext) {
            return navigator.clipboard.writeText(text);
        } else {
            let textArea = document.createElement("textarea");
            textArea.value = text;
            textArea.style.position = "fixed";
            textArea.style.left = "-999999px";
            textArea.style.top = "-999999px";
            document.body.appendChild(textArea);
            textArea.focus();
            textArea.select();
            return new Promise((res, rej) => {
                document.execCommand('copy') ? res() : rej();
                textArea.remove();
            });
        }
    }

    function toast_msg(item, msg) {
        let toast = document.createElement('span');
        toast.className = 'toast';
        toast.style.fontSize = '0.8em';
        toast.innerText = '\n' + msg;
        item.appendChild(toast);
        setTimeout(function () {
            toast.remove();
        }, 500);
    }

    function blink_title(title_text, times = 0) {
        let space = ('—').repeat(title_text.length);
        if (times === 0) {
            document.title = space;
        } else if (times === 1) {
            document.title = title_text;
        }
        times = (times + 1) % 2;
        setTimeout(function () {
            blink_title(title_text, times);
        }, 800);
    }

})();