USTC 助手

为 USTC 学生定制的各类实用功能:绕过验证码,自动登录,睿客网性能优化以及更多。

当前为 2023-04-25 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         USTC Helper
// @name:zh-CN   USTC 助手
// @license      gpl-3.0
// @namespace    http://tampermonkey.net/
// @version      0.9.2
// @description  Various useful functions for USTC students: verification code bypass, auto login, rec performance improvement and more.
// @description:zh-CN  为 USTC 学生定制的各类实用功能:绕过验证码,自动登录,睿客网性能优化以及更多。
// @author       PRO
// @match        https://mail.ustc.edu.cn/
// @match        https://mail.ustc.edu.cn/coremail/index.jsp*
// @match        https://passport.ustc.edu.cn/*
// @match        https://rec.ustc.edu.cn/*
// @match        https://recapi.ustc.edu.cn/identity/other_login?*
// @match        https://www.bb.ustc.edu.cn/*
// @match        https://jw.ustc.edu.cn/*
// @match        https://young.ustc.edu.cn/login/*
// @match        https://wvpn.ustc.edu.cn/*
// @icon         https://passport.ustc.edu.cn/images/favicon.ico
// @grant        none
// ==/UserScript==

(function () {
    'use strict';
    var uhp_config = {
        passport: {
            enabled: true, // If false, all features will be disabled for passport.ustc.edu.cn
            bypass_code: true, // Whether to bypass verification code or not
            focus: true, // Whether to focus on "Login" button
            service: true // Hint service domain and its credibility
        },
        mail: {
            enabled: true, // If false, all features will be disabled for mail.ustc.edu.cn
            focus: true, // Whether to focus on "Login" button
            domain: 'mail.ustc.edu.cn' // Automatically switch to given mail domain
            // Expected values:
            // 'mail.ustc.edu.cn'
            // 'ustc.edu.cn'
            // 'ah.edu.cn'
            // '' (Do nothing)
        },
        rec: {
            enabled: true, // If false, all features will be disabled for rec.ustc.edu.cn & recapi.ustc.edu.cn
            autologin: true, // Whether automatically clicks login (USTC cas login)
            opencurrent: true // Whether open links in current tab (Significantly improves performance)
        },
        bb: {
            enabled: true, // If false, all features will be disabled for www.bb.ustc.edu.cn
            autoauth: true, // Whether automatically authenticate when accessing outside school net
            autologin: true, // Whether automatically clicks login
            showhwstatus: true // Whether to display homework status (may consume some traffic)
        },
        jw: {
            enabled: true, // ...
            login: 'focus', // What to do to the login button: 'none', 'focus', 'click'
            shortcut: true // Shortcut support
        },
        young: {
            enabled: true,
            default_tab: "/myproject/SignUp", // The tab on entering
            auto_tab: true, // Auto navigate to frequently-used submenu
            no_datascreen: true, // Remove annoying data screen image
            shortcut: true // Shortcut support
        },
        wvpn: {
            enabled: true,
            custom_collection: true, // Allows for customizing collections
            collection_tip_time: 2000 // Lasting time for tips (ms)
        }
    };
    switch (window.location.host) {
        case 'mail.ustc.edu.cn': {
            if (!uhp_config.mail.enabled) {
                console.info("[USTC Helper] 'mail' feature disabled.");
                break;
            }
            if (uhp_config.mail.domain) {
                changeDomain(uhp_config.mail.domain);
                console.info(`[USTC Helper] Domain changed to ${uhp_config.mail.domain}.`);
            }
            if (uhp_config.mail.focus) {
                document.getElementById("login_button").focus();
                console.info("[USTC Helper] Login button focused.");
            }
            break;
        }
        case 'passport.ustc.edu.cn': {
            if (!uhp_config.passport.enabled) {
                console.info("[USTC Helper] 'passport' feature disabled.");
                break;
            }
            let form = document.getElementsByClassName('loginForm')[0];
            if (!form) {
                console.log("[USTC Helper] Form not found!");
                break;
            }
            let options = {
                childList: true,
                attributes: false,
                subtree: true
            }
            function bypass() {
                let showCode = document.getElementsByName('showCode')[0];
                showCode.value = "";
                let code = document.querySelector('#valiCode');
                if (code) {
                    code.remove();
                    console.info("[USTC Helper] Verification code bypassed.");
                } else {
                    console.info("[USTC Helper] Verification code not found.");
                }
            }
            function focus() {
                document.getElementById('login').focus();
                console.info("[USTC Helper] Login button focused.");
            }
            function hint() {
                let notice = document.createElement('p');
                let params = new URL(window.location.href).searchParams;
                let service_url = params.get('service');
                if (!service_url) return;
                service_url = decodeURIComponent(service_url);
                let domain = service_url.split('/')[2];
                let color;
                let status; // Official Student/Staff Third-party
                let suffix;
                if (/.+\.ustc\.edu\.cn/.test(domain)) {
                    if (domain == 'home.ustc.edu.cn') {
                        status = "Student";
                        color = "#d0d01b";
                        suffix = "@mail.ustc.edu.cn";
                    } else if (domain == 'staff.ustc.edu.cn') {
                        status = "Staff";
                        color = "#d0d01b";
                        suffix = "@ustc.edu.cn";
                    } else {
                        status = "Official";
                        color = "green";
                    }
                } else {
                    status = "Third-party";
                    color = "red";
                }
                console.info(`[USTC Helper] ${status} service: ${service_url}`);
                if (color == "#d0d01b") {
                    let regex = new RegExp(/https?:\/\/(home|staff)\.ustc\.edu\.cn\/~([^\/]+)/i);
                    let match = service_url.match(regex);
                    if (match) {
                        let name = match[2];
                        let email = name + suffix;
                        console.log("[USTC Helper] Contact email: " + email);
                        notice.innerHTML = `<a style="color: #d0d01b;" title="Contact" href="mailto:${email}">${status}</a> service: <span style="color: grey;" title="${service_url}">${domain}</span>`;
                    } else {
                        console.log("[USTC Helper] Unable to determine contact email!");
                        notice.innerHTML = `<a style="color: #d0d01b;" title="Unrecognized">${status}</a> service: <span style="color: grey;" title="${service_url}">${domain}</span>`;
                    }
                } else {
                    notice.innerHTML = `<span style="color: ${color};">${status}</span> service: <span style="color: grey;" title="${service_url}">${domain}</span>`;
                }
                let main_card = document.getElementsByClassName('card')[0];
                main_card.insertAdjacentElement('afterbegin', notice);
            }
            function main() {
                if (uhp_config.passport.bypass_code) bypass();
                if (uhp_config.passport.focus) focus();
                if (uhp_config.passport.service) hint();
                observer.disconnect();
            }
            let observer = new MutationObserver(main);
            observer.observe(form, options);
            break;
        }
        case 'rec.ustc.edu.cn': {
            if (!uhp_config.rec.enabled) {
                console.info("[USTC Helper] 'rec' feature disabled.");
                break;
            }
            if (uhp_config.rec.opencurrent) {
                window.webpackJsonp.push_ = window.webpackJsonp.push;
                window.webpackJsonp.push = (val) => {
                    if (val[0][0] !== "chunk-5ae262a1")
                        return window.webpackJsonp.push_(val);
                    else { // Following script is adapted from https://rec.ustc.edu.cn/js/chunk-5ae262a1.b84e1461.js
                        val[1]["2c03"] = function (t, e, s) {
                            "use strict";
                            (function (t) {
                                s("55dd");
                                var r = s("a67e");
                                e["a"] = {
                                    name: "GroupLister",
                                    components: {
                                        GroupCreate: function () {
                                            return Promise.all([s.e("chunk-390136ce"), s.e("chunk-662e27b9")]).then(s.bind(null, "18fa"))
                                        },
                                        GroupAdd: function () {
                                            return s.e("chunk-5b916374").then(s.bind(null, "c1c7"))
                                        },
                                        GroupEdit: function () {
                                            return Promise.all([s.e("chunk-390136ce"), s.e("chunk-0daeb591")]).then(s.bind(null, "1fa6"))
                                        }
                                    },
                                    data: function () {
                                        return {
                                            status: {
                                                GroupCreateStatus: !1,
                                                GroupAddStatus: !1,
                                                GroupEditStatus: !1
                                            },
                                            loading: !1,
                                            nothing: !1,
                                            group: {},
                                            sortBy: {},
                                            headers: [{
                                                id: 1,
                                                title: "群名称",
                                                class: "groupname",
                                                sort: "asc",
                                                showSort: !0,
                                                field: "group_name"
                                            }, {
                                                id: 2,
                                                title: "群号",
                                                class: "groupid",
                                                sort: "des",
                                                showSort: !1,
                                                field: "group_number"
                                            }, {
                                                id: 3,
                                                title: "成员",
                                                class: "groupuser",
                                                sort: "des",
                                                showSort: !1,
                                                field: "group_memeber_count"
                                            }, {
                                                id: 5,
                                                title: "分享",
                                                class: "groupshare",
                                                sort: "des",
                                                showSort: !1,
                                                field: "group_share_file_count"
                                            }, {
                                                id: 6,
                                                title: "操作",
                                                class: "groupmenu",
                                                sort: "",
                                                showSort: !1
                                            }]
                                        }
                                    },
                                    created: function () {
                                        this.sortBy = this.headers[0],
                                            this.getGroups()
                                    },
                                    computed: {
                                        userInfo: function () {
                                            return this.$store.state.user.userInfo
                                        }
                                    },
                                    watch: {
                                        $route: function () {
                                            this.getGroups()
                                        }
                                    },
                                    filters: {
                                        identityNameFilter: function (t) {
                                            var e;
                                            switch (t) {
                                                case "owner":
                                                    e = "群主";
                                                    break;
                                                case "admin":
                                                    e = "管理员";
                                                    break;
                                                case "user":
                                                    e = "成员";
                                                    break;
                                                default:
                                                    break
                                            }
                                            return e
                                        }
                                    },
                                    methods: {
                                        createGroup: function () {
                                            t("#newgroup").modal("show")
                                        },
                                        addGroup: function () {
                                            t("#addgroup").modal("show")
                                        },
                                        invite: function (t) {
                                            var e = this.$router.resolve({
                                                name: "group",
                                                params: {
                                                    groupNumber: t.group_number
                                                }
                                            });
                                            this.$confirm({
                                                showYesBtn: !1,
                                                showCopyBtn: !0,
                                                copyBtnText: "复制文字",
                                                title: "邀请入群",
                                                type: "confirm",
                                                content: "打开链接进入群组主页即可申请加入群组:".concat(t.group_name, ",群组主页链接:").concat(window.location.origin).concat(e.href)
                                            }).then((function () { }
                                            )).catch((function () { }
                                            ))
                                        },
                                        goToGroupCloud: function (t, e) {
                                            if (["owner", "admin", "user"].indexOf(t.group_member_identity) < 0)
                                                return this.$message({
                                                    type: "warning",
                                                    message: "您不是组群成员,无法进入群盘"
                                                }),
                                                    !1;
                                            this.$store.commit("setSetting", {
                                                from: !0,
                                                drive: "groupdisk",
                                                tab: e,
                                                group: t
                                            }),
                                                this.$router.push({
                                                    name: "groupDisk",
                                                    params: {
                                                        groupNumber: t.group_number
                                                    }
                                                })
                                        },
                                        isShowMenu: function (t) {
                                            return ["owner", "admin", "user"].indexOf(t.group_member_identity) > -1
                                        },
                                        isEditGroup: function (t) {
                                            return ["owner", "admin"].indexOf(t.group_member_identity) > -1
                                        },
                                        goToGroup: function (t) {
                                            var e = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : "group";
                                            if ("wait" === t.group_is_review)
                                                return this.$message({
                                                    type: "warning",
                                                    message: "群组待审核,不允许操作!"
                                                }),
                                                    !1;
                                            if ("refuse" === t.group_is_review)
                                                return this.$message({
                                                    type: "warning",
                                                    message: "群组审核未通过,不允许操作!"
                                                }),
                                                    !1;
                                            // Instead of opening in new tab, we prefer to use vue's solution
                                            // Modifiy start
                                            this.$router.replace({
                                                name: e,
                                                params: {
                                                    groupNumber: t.group_number
                                                }
                                            });
                                            // Modify end
                                        },
                                        goToGroupHome: function (t) {
                                            this.$store.commit("SET_GROUP_SHOWDESC", !1),
                                                this.$router.push({
                                                    name: "group",
                                                    params: {
                                                        groupNumber: t
                                                    }
                                                })
                                        },
                                        handleEditGroup: function (e) {
                                            var s = this;
                                            Object(r["g"])(e.group_number).then((function (t) {
                                                s.group = t.entity
                                            }
                                            )).catch((function (t) {
                                                s.$message({
                                                    type: "error",
                                                    message: t
                                                })
                                            }
                                            )),
                                                t("#editgroup").modal("show")
                                        },
                                        groupRefresh: function () {
                                            this.getGroups()
                                        },
                                        sortGroup: function (t) {
                                            if (6 === t)
                                                return !1;
                                            var e = this;
                                            this.headers.map((function (s) {
                                                return s.id === t ? (s.showSort = !0,
                                                    s.sort = "des" === s.sort ? "asc" : "des",
                                                    e.sortBy = s,
                                                    s) : (s.showSort = !1,
                                                        s.sort = "des",
                                                        s)
                                            }
                                            )),
                                                this.sortGroupBy()
                                        },
                                        getGroups: function () {
                                            var t = this;
                                            this.groups = [],
                                                this.loading = !0,
                                                this.nothing = !1,
                                                Object(r["r"])({}).then((function (e) {
                                                    if (200 === e.status_code)
                                                        if (t.loading = !1,
                                                            t.groups = e.entity.datas,
                                                            e.entity.total > 0) {
                                                            var s = 0;
                                                            e.entity.datas.map((function (t) {
                                                                "user" != t.group_member_identity && t.group_pending_member_count > 0 && (s += t.group_pending_member_count)
                                                            }
                                                            )),
                                                                t.$store.commit("setRequestNums", s),
                                                                t.sortGroupBy(!0)
                                                        } else
                                                            t.nothing = !0;
                                                    else
                                                        t.$message({
                                                            type: "error",
                                                            message: e.message
                                                        })
                                                }
                                                )).catch((function (e) {
                                                    t.$message({
                                                        type: "error",
                                                        message: e
                                                    })
                                                }
                                                ))
                                        },
                                        sortGroupBy: function () {
                                            var t = this
                                                , e = arguments.length > 0 && void 0 !== arguments[0] && arguments[0];
                                            this.groups.sort((function (s, r) {
                                                var o;
                                                return o = e ? r.group_is_review.localeCompare(s.group_is_review) : "group_name" === t.sortBy.field ? s[t.sortBy.field].localeCompare(r[t.sortBy.field]) : s[t.sortBy.field] - r[t.sortBy.field],
                                                    o = "asc" === t.sortBy.sort ? o : -o,
                                                    o
                                            }
                                            ))
                                        },
                                        groupCancel: function (t) {
                                            var e = this
                                                , s = "adopt" === t.group_is_review ? "解散" : "删除";
                                            this.$confirm({
                                                type: "confirm",
                                                content: "".concat(s, "群后,所有关于本群组的信息都将被删除且无法恢复,确定").concat(s, "【").concat(t.group_name, "】吗?"),
                                                showCancleBtn: !0,
                                                showYesBtn: !0,
                                                custom: []
                                            }).then((function () {
                                                Object(r["u"])({
                                                    groups_list: [t.group_number]
                                                }).then((function (t) {
                                                    200 === t.status_code ? (e.$message({
                                                        type: "success",
                                                        message: t.message
                                                    }),
                                                        e.getGroups()) : e.$message({
                                                            type: "error",
                                                            message: t.message
                                                        })
                                                }
                                                )).catch((function (t) {
                                                    e.$message({
                                                        type: "error",
                                                        message: t
                                                    })
                                                }
                                                ))
                                            }
                                            )).catch((function () { }
                                            ))
                                        },
                                        groupQuit: function (t) {
                                            var e = this;
                                            this.$confirm({
                                                type: "confirm",
                                                content: "确定退出该群组吗?",
                                                showCancleBtn: !0,
                                                showYesBtn: !0,
                                                custom: []
                                            }).then((function () {
                                                Object(r["v"])({
                                                    group_number: t,
                                                    action: "quit",
                                                    members_list: [e.userInfo.user_number]
                                                }).then((function (t) {
                                                    200 === t.status_code ? (e.$message({
                                                        type: "success",
                                                        message: t.message
                                                    }),
                                                        e.getGroups()) : e.$message({
                                                            type: "error",
                                                            message: t.message
                                                        })
                                                }
                                                )).catch((function (t) {
                                                    e.$message({
                                                        type: "error",
                                                        message: t
                                                    })
                                                }
                                                ))
                                            }
                                            )).catch((function () { }
                                            ))
                                        }
                                    },
                                    mounted: function () {
                                        var t = this;
                                        setTimeout((function () {
                                            for (var e in t.status)
                                                t.status[e] = !0
                                        }
                                        ), 500)
                                    }
                                }
                            }
                            ).call(this, s("1157"))
                        };
                        // console.log(val);
                        return window.webpackJsonp.push_(val);
                    }
                };
            }
            if (uhp_config.rec.autologin && document.location.pathname == '/') {
                let app = document.getElementById("app");
                let options = {
                    childList: true,
                    attributes: false,
                    subtree: true
                }
                let observer = new MutationObserver(() => {
                    let btn = document.getElementsByClassName('navbar-login-btn')[0];
                    if (btn) {
                        btn.click();
                        observer.disconnect();
                    }
                });
                observer.observe(app, options);
            } else if (uhp_config.rec.opencurrent) {
                let app = document.getElementById("app");
                let options = {
                    childList: true,
                    attributes: false,
                    subtree: true
                }
                let observer = new MutationObserver(() => {
                    let l = document.getElementsByClassName("app-list").length;
                    if (l) {
                        let links = app.getElementsByTagName("a");
                        for (let link of links) {
                            if (link.target == '_blank') link.removeAttribute("target");
                        }
                    }
                });
                observer.observe(app, options);
            }
            break;
        }
        case 'recapi.ustc.edu.cn': {
            if (!uhp_config.rec.enabled) {
                console.info("[USTC Helper] 'rec' feature disabled.");
                break;
            }
            if (uhp_config.rec.autologin) {
                let btn = document.querySelector("#ltwo > div > button");
                if (!btn) {
                    console.error("[USTC Helper] Login button not found!");
                } else {
                    btn.click();
                }
            }
            break;
        }
        case 'www.bb.ustc.edu.cn': {
            if (!uhp_config.bb.enabled) {
                console.info("[USTC Helper] 'bb' feature disabled.");
                break;
            }
            if (window.location.pathname == '/nginx_auth/' && uhp_config.bb.autoauth) {
                document.getElementsByTagName('a')[0].click();
            } else if ((window.location.pathname == '/' || window.location.pathname == '/webapps/login/') && uhp_config.bb.autologin) {
                document.querySelector('#login > table > tbody > tr > td:nth-child(2) > span > a').click();
            } else if (uhp_config.bb.showhwstatus && window.location.pathname == '/webapps/blackboard/content/listContent.jsp' && document.getElementById('pageTitleText').children[0].textContent == '作业区') {
                let hw_list = document.getElementById('content_listContainer');
                let color_config = ['grey', 'green', 'red', 'yellow'];
                let hint_text = ['查询中', '已提交', '未提交', '查询错误'];
                // let hint_text = ['Checking', 'Submitted', 'Not submitted', 'Error'];
                async function query_status(link) {
                    const r = await fetch(link);
                    if (!r.ok) {
                        console.log(`[USTC Helper] Failed to fetch "${r.url}": ${r.status} ${r.statusText}`);
                        return 3;
                    } else {
                        let html = await r.text();
                        if (html.match(/<span id="pageTitleText">\n  复查提交历史记录: .+<\/span>/)) return 1;
                        else if (html.match(/<span id="pageTitleText">\n  上载作业:.+<\/span>/)) return 2;
                        else return 3;
                    }
                }
                async function process(hw) {
                    let link_ = hw.querySelector("h3 > a");
                    if (link_) {
                        let status = 0; // 0: Checking  1: Uploaded  2: Not uploaded  3: Error
                        let hint = document.createElement('span');
                        let ret = '';
                        hint.style.color = color_config[status];
                        hint.textContent = `(${hint_text[status]})`;
                        link_.appendChild(hint);
                        let link = link_.href;
                        // https://www.bb.ustc.edu.cn/webapps/assignment/uploadAssignment?content_id=_106763_1&course_id=_12559_1&group_id=&mode=view
                        let params = new URL(link).searchParams;
                        let course_id = params.get("course_id");
                        let content_id = params.get("content_id");
                        let uploaded = sessionStorage.getItem(course_id);
                        // Query from cache first
                        if (uploaded) {
                            uploaded = JSON.parse(uploaded);
                            if (uploaded.indexOf(content_id) >= 0) {
                                status = 1;
                                console.log(`[USTC Helper] "${course_id}/${content_id}" present in cache, so this homework is uploaded.`);
                            }
                        }
                        // Not in cache
                        if (!status) {
                            status = await query_status(link);
                            if (status == 1) {
                                ret = content_id;
                                console.log(`[USTC Helper] Online query indicated that "${course_id}/${content_id}" is uploaded.`);
                            } else if (status == 2) {
                                console.log(`[USTC Helper] Online query indicated that "${course_id}/${content_id}" is not uploaded.`);
                            } else {
                                console.warn(`[USTC Helper] Online query "${course_id}/${content_id}" failed!`);
                            }
                        }
                        hint.style.color = color_config[status];
                        hint.textContent = `(${hint_text[status]})`;
                        return ret;
                    }
                }
                let promises = [];
                for (let hw of hw_list.children) {
                    promises.push(process(hw));
                }
                Promise.all(promises).then(
                    (values) => {
                        let params = new URL(window.location.href).searchParams;
                        let course_id = params.get('course_id');
                        let uploaded = sessionStorage.getItem(course_id);
                        if (uploaded) {
                            uploaded = JSON.parse(uploaded);
                        } else {
                            uploaded = [];
                        }
                        for (let content_id of values) {
                            if (content_id.length) {
                                uploaded.push(content_id);
                                console.log(`[USTC Helper] Saving "${course_id}/${content_id}" to cache...`);
                            }
                        }
                        sessionStorage.setItem(course_id, JSON.stringify(uploaded));
                    }
                );
            }
            break;
        }
        case 'jw.ustc.edu.cn': {
            if (!uhp_config.jw.enabled) {
                console.info("[USTC Helper] 'jw' feature disabled.");
                break;
            }
            if (uhp_config.jw.login && window.location.pathname == "/login") {
                let btn = document.getElementById('login-unified-wrapper');
                if (uhp_config.jw.login == 'focus') {
                    btn.focus();
                } else if (uhp_config.jw.login == 'click') {
                    btn.click();
                } else {
                    console.error(`[USTC Helper] Unknown option for jw.login: ${uhp_config.jw.login}`);
                }
            }
            if (uhp_config.jw.shortcut) {
                let shortcuts = ["ArrowLeft", "ArrowRight", "x", '1', '2', '3', '4', '5', '6', '7', '8', '9'];
                document.addEventListener("keydown", (e) => {
                    if (document.activeElement.nodeName != "INPUT" &&
                        shortcuts.includes(e.key)) {
                        let menu = window.top.document.getElementById("e-home-tab-list");
                        let tabs = Array.from(menu.children);
                        let home = window.top.document.querySelector("#e-top-home-page > li > a");
                        tabs.push(home);
                        let count = tabs.length;
                        let current = 0;
                        for (let tab of tabs) {
                            if (tab.classList.contains('active')) {
                                break;
                            }
                            current++;
                        }
                        if (current == count) current--;
                        switch (e.key) {
                            case "ArrowLeft":
                                tabs[(current - 1 + count) % count].click();
                                break;
                            case "ArrowRight":
                                tabs[(current + 1) % count].click();
                                break;
                            case "x":
                                let close = tabs[current].querySelector("a > i")
                                if (close) close.click();
                                break;
                            default:
                                if (e.key.length == 1) {
                                    let idx = (Number(e.key) - 2 + count) % count;
                                    if (0 <= idx && idx < count) {
                                        tabs[idx].click();
                                    }
                                }
                                break;
                        }
                    }
                });
            }
            break;
        }
        case 'young.ustc.edu.cn': {
            if (!uhp_config.young.enabled) {
                console.info("[USTC Helper] 'young' feature disabled.");
                break;
            }
            let app = document.getElementById("app");
            let router = app.__vue__.$router;
            function main(mutations, observer) {
                let menu = app.querySelector(".ant-menu-root");
                if (!menu) return;
                let default_tab = uhp_config.young.default_tab;
                if (default_tab.length)
                    router.push(default_tab);
                let submenus = menu.querySelectorAll("li.ant-menu-submenu-horizontal:not(.ant-menu-overflowed-submenu) > div");
                if (!submenus.length) return;
                observer.disconnect();
                if (uhp_config.young.auto_tab) {
                    submenus[0].onclick = (e) => {
                        router.push('/dataAnalysis/studentAnalysis');
                        e.stopImmediatePropagation();
                    }
                    submenus[1].onclick = (e) => {
                        router.push('/personalInformation/personalReport');
                    }
                    submenus[2].onclick = (e) => {
                        router.push('/myproject/SignUp');
                    }
                    submenus[5].onclick = (e) => {
                        router.push('/isystem/departUserList');
                    }
                    app.querySelector(".user-dropdown-menu").onclick = (e) => {
                        document.querySelector("ul.user-dropdown-menu-wrapper > li:nth-child(7) > a").click();
                    }
                }
                if (uhp_config.young.no_datascreen) {
                    app.querySelector("div.header-index-wide > a").remove();
                }
                if (uhp_config.young.shortcut) {
                    document.addEventListener("keydown", (e) => {
                        let tabs = document.querySelector(".ant-tabs-nav-animated > div").children;
                        let count = tabs.length;
                        let current = 0;
                        for (let tab of tabs) {
                            if (tab.attributes["aria-selected"].value == "true") {
                                break;
                            }
                            current++;
                        }
                        if (document.activeElement.nodeName != "INPUT") {
                            switch (e.key) {
                                case "ArrowLeft":
                                    tabs[(current - 1 + count) % count].click();
                                    break;
                                case "ArrowRight":
                                    tabs[(current + 1) % count].click();
                                    break;
                                case "x":
                                    tabs[current].querySelector("div > i").click();
                                    break;
                                default:
                                    if (e.key.length == 1) {
                                        let idx = Number(e.key);
                                        if (idx && 0 < idx && idx <= count) {
                                            tabs[idx - 1].click();
                                        }
                                    }
                                    break;
                            }
                        }
                    })
                }
            }
            let options = {
                childList: true,
                attributes: false,
                subtree: true
            }
            let observer = new MutationObserver(main);
            observer.observe(app, options);
            break;
        }
        case 'wvpn.ustc.edu.cn': {
            if (!uhp_config.wvpn.enabled) {
                console.info("[USTC Helper] 'wvpn' feature disabled.");
                break;
            }
            if (uhp_config.wvpn.custom_collection) {
                // let element = document.querySelector("div.portal-search-input-wrap");
                let options = {
                    childList: true,
                    attributes: false,
                    subtree: true
                }
                let callback = (mutations, observer) => {
                    let input = document.querySelector("input.portal-search__input");
                    if (!input) return;
                    observer.disconnect();
                    input.placeholder = "📦 正在加载依赖库...";
                    let node = document.createElement("script");
                    node.src = "https://cdn.bootcdn.net/ajax/libs/aes-js/3.1.2/index.js";
                    function fail(s, hint, recover=true) {
                        console.error("[USTC Helper]", s);
                        input.placeholder = hint;
                        if (recover) {
                            window.setTimeout(() => {
                                input.placeholder = "点击五角星或 Shift+Enter 以收藏 🍻";
                            }, uhp_config.wvpn.collection_tip_time);
                        }
                    }
                    function success(s, hint) {
                        console.info("[USTC Helper]", s);
                        input.placeholder = hint;
                        window.setTimeout(() => {
                            input.placeholder = "点击五角星以进行自定义收藏 🍻";
                        }, uhp_config.wvpn.collection_tip_time);
                    }
                    function cancel() {
                        console.info("[USTC Helper] User calcelled the operation.");
                        input.placeholder = "你终止了收藏操作!😢";
                        window.setTimeout(() => {
                            input.placeholder = "点击五角星或 Shift+Enter 以收藏 🍻";
                        }, uhp_config.wvpn.collection_tip_time);
                    }
                    function invalid() {
                        console.warn("[USTC Helper] Invalid input!");
                        input.placeholder = "你输入了一个不合法的值!🤔";
                        window.setTimeout(() => {
                            input.placeholder = "点击五角星或 Shift+Enter 以收藏 🍻";
                        }, uhp_config.wvpn.collection_tip_time);
                    }
                    node.onload = () => {
                        success("Aes-js loaded.", "成功加载依赖库!🥳");
                        // Adapted from https://blog.csdn.net/lijiext/article/details/110931285
                        var utf8 = aesjs.utils.utf8;
                        var hex = aesjs.utils.hex
                        var AesCfb = aesjs.ModeOfOperation.cfb
                        var wrdvpnKey = 'wrdvpnisthebest!'
                        var wrdvpnIV = 'wrdvpnisthebest!'
                        function textRightAppend(text, mode) {
                            var segmentByteSize = mode === 'utf8' ? 16 : 32

                            if (text.length % segmentByteSize === 0) {
                                return text
                            }

                            var appendLength = segmentByteSize - text.length % segmentByteSize
                            var i = 0
                            while (i++ < appendLength) {
                                text += '0'
                            }
                            return text
                        }
                        function encrypt(text, key, iv) {
                            var textLength = text.length
                            text = textRightAppend(text, 'utf8')
                            var keyBytes = utf8.toBytes(key)
                            var ivBytes = utf8.toBytes(iv)
                            var textBytes = utf8.toBytes(text)
                            var aesCfb = new AesCfb(keyBytes, ivBytes, 16)
                            var encryptBytes = aesCfb.encrypt(textBytes)
                            return hex.fromBytes(ivBytes) + hex.fromBytes(encryptBytes).slice(0, textLength * 2)
                        }
                        function encryptUrl(url) {
                            var port = "";
                            var segments = "";
                            var protocol = "";

                            if (url.startsWith("http://")) {
                                url = url.substr(7);
                                protocol = "http";
                            } else if (url.startsWith("https://")) {
                                url = url.substr(8);
                                protocol = "https";
                            } else {
                                return "";
                            }
                            var v6 = "";
                            var match = /\[[0-9a-fA-F:]+?\]/.exec(url);
                            if (match) {
                                v6 = match[0];
                                url = url.slice(match[0].length);
                            }
                            segments = url.split("?")[0].split(":");
                            if (segments.length > 1) {
                                port = segments[1].split("/")[0]
                                url = url.substr(0, segments[0].length) + url.substr(segments[0].length + port.length + 1);
                            }
                            var i = url.indexOf('/');
                            if (i == -1) {
                                if (v6 != "") {
                                    url = v6;
                                }
                                url = encrypt(url, wrdvpnKey, wrdvpnIV)
                            } else {
                                var host = url.slice(0, i);
                                var path = url.slice(i);
                                if (v6 != "") {
                                    host = v6;
                                }
                                url = encrypt(host, wrdvpnKey, wrdvpnIV) + path;
                            }
                            if (port != "") {
                                url = "/" + protocol + "-" + port + "/" + url;
                            } else {
                                url = "/" + protocol + "/" + url;
                            }
                            return url;
                        }
                        // End
                        function random_color() {
                            let r = Math.floor(Math.random() * 256);
                            let g = Math.floor(Math.random() * 256);
                            let b = Math.floor(Math.random() * 256);
                            return `rgb(${r}, ${g}, ${b})`;
                        }
                        function add_collect() {
                            // Get url
                            let url = input.value;
                            if (url.length == 0) {
                                url = prompt("请输入要收藏的网址:");
                            } else {
                                input.value = '';
                            }
                            if (url == undefined || url == null) {
                                cancel();
                                return;
                            } else if (url.length == 0) {
                                invalid();
                                return;
                            }
                            if (!url.startsWith("http://") && !url.startsWith("https://")) {
                                url = "https://" + url;
                            }
                            let url_;
                            try {
                                url_ = new URL(url);
                            } catch (error) {
                                invalid();
                                return;
                            }
                            // Get name
                            let name = ""; let desc = "";
                            name = prompt("请输入收藏项目的名称:", url_.hostname);
                            if (name == null) {
                                cancel();
                                return;
                            }
                            desc = prompt("请输入收藏项目的备注:", url_.hostname);
                            if (desc == null) {
                                cancel();
                                return;
                            }
                            let id = document.querySelector("div[data-id=collection].block-group > div.block-group__content").childElementCount;
                            let post_data = {
                                "resource_type": "vpn",
                                "name": name,
                                "detail": desc,
                                "url": url,
                                "redirect": encryptUrl(url),
                                "id": id,
                                "group_id": "2",
                                "logo": "",
                                "_isCollect": "false",
                                "_displayName": name,
                                "_desc": desc,
                                "_icon[color]": random_color(),
                                "_icon[content]": name[0]
                            }
                            let form = new FormData();
                            for (let [k, v] of Object.entries(post_data)) {
                                form.append(k, v);
                            }
                            fetch("/user/portal/collections", {
                                method: "POST",
                                body: form
                            })
                                .then(r => r.json(), r => {
                                    fail("Failed to add collection due to network error: " + r.toString(), "网络原因,收藏失败!⚠️");
                                })
                                .then(data => {
                                    if (data["data"]) {
                                        success("Successfully added to collection.", "成功加入收藏,刷新以查看效果!🥳");
                                    } else {
                                        fail("Failed to add collection:" + (data["msg"] ? data["msg"] : "Unknown error."), "请求收藏接口失败!⚠️");
                                    }
                                });
                        }
                        let a = document.createElement("a");
                        a.text = "⭐";
                        a.style = "position: absolute;left: 150px;top: 20px;";
                        a.onclick = add_collect;
                        input.parentElement.appendChild(a);
                    }
                    node.onerror = (e) => {
                        fail("Failed to load Aes-js. You won't be able to use \"custom_collection\" feature.", "依赖库加载失败,您将无法使用自定义收藏功能!⚠️", false);
                    }
                    document.head.appendChild(node);
                }
                let observer = new MutationObserver(callback);
                observer.observe(document.body, options);
            }
            break;
        }
        default:
            console.error("[USTC Helper] Unexpected host: " + window.location.host);
            break;
    }
})();