洛谷到 VJudge 跳转脚本

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

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

})();