Greasy Fork is available in English.

vjudge++

为vJudge设置背景,并汉化部分界面

// ==UserScript==
// @name         vjudge++
// @namespace    vjudge-plus-v2
// @version      1.8.4b12.6
// @description  为vJudge设置背景,并汉化部分界面
// @author       axototl (original by Suntra)
// @match        https://vjudge.net/*
// @noframes
// @icon         https://vjudge.net/favicon.ico
// @license      AGPLv3 or later
// @grant        GM_addStyle
// @grant        GM_registerMenuCommand
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_info
// @run-at       document-end
// ==/UserScript==

'use strict';

// license text: https://www.gnu.org/licenses/agpl-3.0.txt
(() => {
    // 在基于 Blink 浏览器上检测是否为正常返回
    if (GM_info.platform.browserName == "chrome" && performance.getEntries()[0].responseStatus != 200) return;

    let config = {};

    function dbgopt(...txt) {
        if (config.debug) console.debug(txt.join(' '));
    }


    function reloader() {
        alert("设置成功,刷新生效");
        if (!navigator.onLine) {
            alert("离线状态,无法重加载。\n修改无法即刻生效");
            return;
        }
        location.reload();
    }


    // 初始化Getter / Setter
    (() => {
        const def_props = {
            experimental: false,
            debug: true,
            ads: true,
            enable_style: true,
            back: "https://cdn.luogu.com.cn/images/bg/fe/Day_And_Night_1.jpg",
            col: "#fff",
            collink: "#ff4c8c"
        }

        function getVal(key) {
            let gg = GM_getValue(key);
            if ('' === gg || undefined === gg) {
                let def = def_props[key];
                GM_setValue(key, def);
                gg = def;
            }
            return gg;
        }
        for (let prop in def_props) {
            Object.defineProperty(config, prop, {
                get: () => getVal(prop),
                set: val => GM_setValue(prop, val)
            });
        }
    })();


    // 获取环境配置(不得异步处理) -Begin-
    (() => {
        function reg_command(name, prompts, need_reload = true) {
            let flag = true;
            GM_registerMenuCommand(prompts[config[name] | 0], () => {
                if (flag) {
                    config[name] = !config[name];
                    flag = false;
                }
                if (need_reload) reloader();
            })
        }
        // 设置实验性功能
        reg_command("experimental", ["× 点击启用实验性功能(界面汉化等)", "✔ 点击关闭实验性功能"]);
        // 设置debug
        reg_command("debug", ["已禁用 debug 输出", "已启用debug输出"], false);
        reg_command("ads", ["× 屏蔽广告(点击启用)", "✔ 屏蔽广告(点击禁用)"]);
        reg_command("enable_style", ["✖ 点击开启美化功能", '✔ 点击关闭美化功能']);
        // 禁用美化功能
        if (!config.enable_style) return;
        // 设置背景
        GM_registerMenuCommand("设置背景URL", () => {
            let tmp = window.prompt("请输入背景URL", config.back);
            if (null === tmp) {
                alert("未更改背景图片URL");
                return;
            }
            config.back = tmp;
            GM_setValue("background", config.back);
            reloader();
        });

        // 设置文字颜色
        const tester = /^#([0-9a-f]{3,6})$/i;

        function getColor(t) {
            let tmp;
            do {
                tmp = window.prompt("请输入颜色的Hexcode\n(比如#b93e3e)\n建议选择背景主色调的反差色", t);
            } while (null !== tmp && !tester.test(tmp) && '' !== tmp);
            if (null === tmp) tmp = t;
            return tmp;
        }
        GM_registerMenuCommand("设置文字颜色", () => {
            config.col = getColor(config.col);
            reloader();
        });
        GM_registerMenuCommand("设置链接背景颜色", () => {
            config.collink = getColor(config.collink);
            reloader();
        });
    })();
    // 获取环境配置 -End-

    // 界面美化程序 -Begin-
    (async () => {
        if (!config.enable_style) return;
        document.body.innerHTML = "<div style='height: 60px'></div>" + document.body.innerHTML; // 防止顶栏和页面内容重叠
        // User defined style
        GM_addStyle("body {background: url(" + config.back + ") no-repeat center top fixed;background-size: 100% 100%;-moz-background-size: 100% 100%;color: " + config.col + ";}" +
            "a:focus, a:hover, .active {&:not(.nav-link){color: " + config.collink + " !important;text-decoration: underline;}}");
        // Global Style
        GM_addStyle(
            ".navbar {border-radius:0rem;background-color: rgba(0,0,0,65%) !important;position: fixed;top: 0;left: 0;z-index: 1000;width: 100%;}" +
            "scrollbar-width: none" +
            ".modal-content {background-color: rgba(255,255,255,90%);}" +
            ".form-control {background-color: rgba(255,255,255,50%);}" +
            ".tab-content {background-color: rgba(255,255,255,50%);border: 2px solid #eceeef;border-radius: 0.25rem;padding: 20px;}" +
            "table {background-color: rgba(255,255,255,70%);border-radius: 0.25rem;color: #000;}"
        );
        GM_addStyle(".card-block, .card, .list-group-item, .btn-secondary, .page-link, .page-item.disabled .page-link, .dropdown-menu {background-color: rgba(255,255,255,0%) !important;}");
        document.querySelector("body > div.body-footer").innerHTML += '<p style="color: #3fb98b">Theme powered by vjudge++ (original <a href="https://greasyfork.org/scripts/448801">vjudge+</a>)</p>';
    })();
    // 界面美化程序 -End-

    (async () => {
        if (!config.ads) return;
        let arr = document.querySelectorAll(".social, #prob-ads, #img-support");
        for (let x of arr) x.remove();
    })(); // 广告移除

    // 界面汉化程序 -Begin-
    (async () => {
        if (!config.experimental) return;
        console.warn("未来版本将分离JSON文件,请注意");
        const basicTranslateTable = {
            "#nav-problem > a": "问题列表",
            "#nav-status > a": "提交记录",
            "#nav-contest > a": "比赛",
            "#nav-workbook > a": "题单",
            "#nav-user > a": "用户",
            "#nav-group > a": "小组",
            "#nav-comment > a": "留言板",
            ".login": "登录",
            ".register": "注册",
            ".logout": "登出",
            ".user-dropdown > a:nth-child(1)": "个人主页",
            ".update-profile": "更新个人信息",
            ".message": "消息"
        };

        const basicDynTransTable = {
            ".previous > a": "上一页",
            ".next > a": "下一页",
            "#filter": "应用过滤器", // 无法工作
            "#reset": "重置过滤器", // 无法工作
        };
        const loginBoxTranslate = {
            "#loginModalLabel": "登录",
            "#btn-forget-password": "忘记密码",
            "#btn-login": "登录",
            ".btn[data-dismiss]": "取消"
        };

        const registerBoxTrans = {
            "#registerModalLabel": "注册",
            "[for=register-username]": "用户名\n(必填)",
            "[for=register-password]": "密码\n(必填)",
            "[for=register-repeat-password]": "重复密码\n(必填)",
            "[for=register-nickname]": "昵称\n(可修改)",
            "[for=register-school]": "学校",
            "[for=register-email]": "邮箱\n(必填)",
            "[for=register-introduction]": "自我介绍",
            "[for=register-captcha]": "验证码\n(必填)",
            "#btn-register": "注册",
            ".btn[data-dismiss]": "取消"
        };

        const updateProfileTrans = {
            "#updateModalLabel": "更新个人信息",
            "[for=update-username]": "用户名",
            "[for=update-orig-password]": "原密码(必填)",
            "[for=update-password]": "新密码\n(可选)",
            "[for=update-repeat-password]": "重复新密码",
            "[for=update-nickname]": "昵称",
            "[for=update-school]": "学校",
            "[for=update-captcha]": "验证码",
            "[for=update-email]": "邮箱",
            "[for=update-introduction]": "个人简介",
            "#btn-update-profile": "更新",
            ".btn[data-dismiss]": "取消"
        };

        function upd_trans(tr, flag = false) {
            for (let prop in tr) {
                let k = document.querySelector(prop);
                if (null != k)
                    if (flag && k.childNodes.length >= 1) k.childNodes[0].data = tr[prop];
                    else {
                        k.innerText = tr[prop];
                    }
                else dbgopt(prop, "is null");
            }
        }

        function dynamic_trans(table, triggerDOM = null) {
            let ev = "click";
            if (null == triggerDOM)
                ev = "load", triggerDOM = window;
            triggerDOM.addEventListener(ev, () => setTimeout(() => upd_trans(table), 200));
        }

        function reg_box_trans(triggerElem_selector, table) {
            let s = document.querySelector(triggerElem_selector);
            if (null != s) dynamic_trans(table, s);
            else dbgopt(triggerElem_selector, "is null");
        }

        (async () => {
            document.querySelector(".navbar-brand").childNodes[2].data = " 首页";
            upd_trans(basicTranslateTable);
            reg_box_trans(".login", loginBoxTranslate);
            reg_box_trans(".register", registerBoxTrans);
            reg_box_trans(".update-profile", updateProfileTrans);
            dynamic_trans(basicDynTransTable);
        })(); //基本汉化

        // 静态内容汉化
        /* -Begin- */
        const staticTransTable = {
            "/": [{
                "#index-intro > div > div > p": "Vritual Judge(以下简称vj)并不是一个真实的在线评测网站(以下简称OJ),\
而是整合了各大OJ平台的题目形成的虚拟OJ平台。你提交的所有代码都会被发回原平台进行评测。\n\
vj可以让你轻松开展比赛,不再为测试数据发愁\n\n\
目前我们支持以下平台的题库:"
            }, 0],
            "/problem": [{
                "[data-category=all]": "全部问题",
                "[data-category=solved]": "已解决问题",
                '[data-category=favorites]': "收藏的问题",
                "[data-category=attempted]": "未通过/正在评测的问题"
            }, 1],
            "/status": [{
                "[data-owner=all]": "所有提交",
                "[data-owner=mine]": "我的提交",
                ".username": "用户名",
                ".oj": "测评平台",
                ".prob_num": "问题编号",
                ".status": "状态",
                ".runtime": "运行时长",
                ".memory": "运行内存",
                ".length": "代码长度",
                ".language": "语言",
                ".date": "提交时间"
            }, 1],
        };
        (async () => {
            for (const path in staticTransTable) {
                if (path == location.pathname) {
                    const tr = staticTransTable[path]
                    upd_trans(tr[0], tr[1]);
                    break;
                }
            }
        })();
        /* -End- */
    })();
    // 界面汉化程序 -End-
})();