洛谷到 VJudge 跳转脚本

在洛谷问题页面的特定位置添加一个跳转到 VJudge 的链接

目前為 2024-10-18 提交的版本,檢視 最新版本

// ==UserScript==
// @name         洛谷到 VJudge 跳转脚本
// @namespace    http://tampermonkey.net/
// @version      2.1
// @description  在洛谷问题页面的特定位置添加一个跳转到 VJudge 的链接
// @author       Kimi
// @match        https://www.luogu.com.cn/problem/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    /**
     * 提取当前页面的题目代码
     * @returns {string|null} 题目代码或 null
     */
    function getProblemCode() {
        const path = window.location.pathname;
        const match = path.match(/^\/problem\/([^/]+)/);
        return match ? match[1] : null;
    }

    /**
     * 根据题目代码生成对应的 VJudge URL
     * @param {string} problemCode - 题目代码
     * @returns {string|null} VJudge 的 URL 或 null
     */
    function getVJudgeURL(problemCode) {
        let match;
        if ((match = problemCode.match(/^(P|B)(\d+)$/))) {
            const prefix = '洛谷';
            return `https://vjudge.net/problem/${encodeURIComponent(`${prefix}-${problemCode}`)}`;
        }
        if ((match = problemCode.match(/^(UVA)(\d+)$/))) {
            return `https://vjudge.net/problem/${encodeURIComponent(`UVA-${match[2]}`)}`;
        }
        if ((match = problemCode.match(/^(CF)(\d+[A-Za-z]*)$/))) {
            return `https://vjudge.net/problem/${encodeURIComponent(`CodeForces-${match[2]}`)}`;
        }
        if ((match = problemCode.match(/^(SP)(\d+)$/))) {
            const span = document.querySelector('span[title*=" - "]');
            if (span) {
                const title = span.getAttribute('title');
                const contestName = title.split(' - ')[0];
                return `https://vjudge.net/problem/${encodeURIComponent(`SPOJ-${contestName}`)}`;
            }
            return null;
        }
        if ((match = problemCode.match(/^AT_(.+)$/))) {
            return `https://vjudge.net/problem/${encodeURIComponent(`AtCoder-${match[1]}`)}`;
        }
        if ((match = problemCode.match(/^AT(.+)$/))) {
            return `https://vjudge.net/problem/${encodeURIComponent(`AtCoder-${match[1]}`)}`;
        }
        return null;
    }

    /**
     * 在页面中注入 VJudge 的链接
     * @param {string} vjudgeURL - VJudge 的 URL
     */
    function injectVJudgeLink(vjudgeURL) {
        const actionDiv = document.querySelector('div.action');
        if (actionDiv) {
            // 检查是否已经注入过链接,避免重复
            if (!actionDiv.querySelector('a.vjudge-link')) {
                const link = document.createElement('a');
                link.href = vjudgeURL;
                link.target = '_blank';
                link.style.marginLeft = '10px';
                link.textContent = '跳转至 VJudge';
                link.classList.add('vjudge-link'); // 添加类名以便后续检查
                actionDiv.appendChild(link);
                console.log('VJudge链接已成功注入:', vjudgeURL);
            }
        } else {
            console.warn('Luogu to VJudge Script: 未找到目标元素 "div.action"。');
        }
    }

    /**
     * 主函数,负责整体流程
     */
    function main() {
        const problemCode = getProblemCode();
        if (!problemCode) {
            console.warn('Luogu to VJudge Script: 无法提取问题代码。');
            return;
        }
        const vjudgeURL = getVJudgeURL(problemCode);
        if (!vjudgeURL) {
            console.warn(`Luogu to VJudge Script: 未找到问题代码 "${problemCode}" 对应的 VJudge 映射。`);
            return;
        }
        injectVJudgeLink(vjudgeURL);
    }

    /**
     * 使用 MutationObserver 监听 DOM 变化,确保目标元素出现后执行注入
     */
    function observeDOM() {
        const observer = new MutationObserver((mutations, obs) => {
            const actionDiv = document.querySelector('div.action');
            if (actionDiv) {
                main();
                obs.disconnect(); // 注入后停止观察
            }
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    }

    /**
     * 初始化脚本,根据当前文档状态决定执行方式
     */
    function init() {
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', () => {
                main();
                observeDOM(); // 在初始执行后继续观察,以防动态加载
            });
        } else {
            main();
            observeDOM();
        }
    }

    // 立即执行初始化
    init();

})();