洛谷到 VJudge 跳转脚本

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

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

(function() {
    'use strict';

    function getProblemCode() {
        const path = window.location.pathname;
        const match = path.match(/^\/problem\/([^/]+)/);
        return match ? match[1] : null;
    }

    function getVJudgeURL(problemCode) {
        let match;
        if ((match = problemCode.match(/^(CF)(\d+)([A-Za-z]*)(\d*)$/))) {
            // 处理 CF + 数字 + 字母 + 可选数字
            return `https://vjudge.net/problem/${encodeURIComponent(`CodeForces-${match[2]}${match[3]}${match[4]}`)}#author=GPT_zh`;
        }
        if ((match = problemCode.match(/^(P|B)(\d+)$/))) {
            const prefix = '洛谷';
            return `https://vjudge.net/problem/${encodeURIComponent(`${prefix}-${problemCode}`)}#author=GPT_zh`;
        }
        if ((match = problemCode.match(/^(UVA)(\d+)$/))) {
            return `https://vjudge.net/problem/${encodeURIComponent(`UVA-${match[2]}`)}#author=GPT_zh`;
        }
        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}`)}#author=GPT_zh`;
            }
            return null;
        }
        if ((match = problemCode.match(/^AT_(.+)$/))) {
            return `https://vjudge.net/problem/${encodeURIComponent(`AtCoder-${match[1]}`)}#author=GPT_zh`;
        }
        if ((match = problemCode.match(/^AT(.+)$/))) {
            return `https://vjudge.net/problem/${encodeURIComponent(`AtCoder-${match[1]}`)}#author=GPT_zh`;
        }
        return null;
    }

    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);
    }

    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();

})();