CodeChef/QOJ/Codeforces → VJudge/Luogu Redirect

在 CodeChef、QOJ、Codeforces 题目页提供跳转到对应 VJudge/Luogu 题目的按钮(可切换为自动跳转)

// ==UserScript==
// @name         CodeChef/QOJ/Codeforces → VJudge/Luogu Redirect
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  在 CodeChef、QOJ、Codeforces 题目页提供跳转到对应 VJudge/Luogu 题目的按钮(可切换为自动跳转)
// @author       znzryb
// @match        https://www.codechef.com/problems/*
// @match        https://qoj.ac/problem/*
// @match        https://qoj.ac/contest/*/problem/*
// @match        https://codeforces.com/contest/*/problem/*
// @match        https://codeforces.com/problemset/problem/*/*
// @match        https://codeforces.com/gym/*/problem/*
// @grant        none
// @license      GPL-3.0
// @run-at       document-idle
// ==/UserScript==
(function () {
  'use strict';
  
  /** 工具函数:创建并挂载按钮(避免重复) */
  function createJumpButton(targetUrl, label = 'Jump to VJudge', buttonId = 'vj-redirect-btn', topOffset = '100px') {
    if (!targetUrl) return;
    if (document.getElementById(buttonId)) return;
    const btn = document.createElement('button');
    btn.id = buttonId;
    btn.textContent = label;
    Object.assign(btn.style, {
      position: 'fixed',
      top: topOffset,
      right: '20px',
      padding: '10px 15px',
      backgroundColor: '#28a745',
      color: '#fff',
      border: 'none',
      borderRadius: '6px',
      cursor: 'pointer',
      fontSize: '14px',
      zIndex: 10000,
      boxShadow: '0 2px 10px rgba(0,0,0,0.15)',
    });
    btn.onmouseenter = () => (btn.style.opacity = '0.9');
    btn.onmouseleave = () => (btn.style.opacity = '1');
    btn.addEventListener('click', () => {
      window.open(targetUrl, '_blank');
    });
    document.body.appendChild(btn);
  }
  
  /** 解析当前站点并生成对应的 VJudge URL */
  const { host, pathname } = window.location;
  
  // —— CodeChef ————————————————————————————————————————————————
  // URL 一般为 /problems/ABCDEF 或 /problems/ABCDEF?tab=statement
  if (host.includes('codechef.com')) {
    const parts = pathname.split('/').filter(Boolean); // ['problems','ABCDEF']
    let problemCode = parts[1] || '';                  // 索引 1 为题号
    // 去掉可能存在的查询串(通常问题代码不含 ?,此处保险处理)
    problemCode = problemCode.split('?')[0];
    if (!problemCode) return;
    const vjudgeUrl = `https://vjudge.net/problem/CodeChef-${problemCode}`;
    // === AUTO REDIRECT(可选)===
    // 如果需要自动跳转,取消下面一行注释:
    // window.location.href = vjudgeUrl;
    // 否则创建按钮
    createJumpButton(vjudgeUrl, 'Jump to VJudge');
    return;
  }
  
  // —— QOJ ————————————————————————————————————————————————
  // 支持:
  //   /problem/14548
  //   /contest/2521/problem/14501
  if (host === 'qoj.ac') {
    let qojPid = '';
    // 形式1:/problem/:pid
    let m = pathname.match(/^\/problem\/(\d+)(?:\/)?$/);
    if (m) {
      qojPid = m[1];
    } else {
      // 形式2:/contest/:cid/problem/:pid
      m = pathname.match(/^\/contest\/(\d+)\/problem\/(\d+)(?:\/)?$/);
      if (m) {
        qojPid = m[2];
      }
    }
    if (!qojPid) return;
    const vjudgeUrl = `https://vjudge.net/problem/QOJ-${qojPid}`;
    // === AUTO REDIRECT(可选)===
    // 如果需要自动跳转,取消下面一行注释:
    // window.location.href = vjudgeUrl;
    createJumpButton(vjudgeUrl, 'Jump to VJudge');
    return;
  }
  
  // —— Codeforces ————————————————————————————————————————————————
  // 支持:
  //   /contest/2120/problem/D
  //   /problemset/problem/2120/D
  //   /gym/12345/problem/A
  if (host === 'codeforces.com') {
    let contestId = '';
    let problemIndex = '';
    
    // 形式1:/contest/:cid/problem/:index
    let m = pathname.match(/^\/contest\/(\d+)\/problem\/([A-Z]\d?)(?:\/)?$/i);
    if (m) {
      contestId = m[1];
      problemIndex = m[2];
    } else {
      // 形式2:/problemset/problem/:cid/:index
      m = pathname.match(/^\/problemset\/problem\/(\d+)\/([A-Z]\d?)(?:\/)?$/i);
      if (m) {
        contestId = m[1];
        problemIndex = m[2];
      } else {
        // 形式3:/gym/:cid/problem/:index
        m = pathname.match(/^\/gym\/(\d+)\/problem\/([A-Z]\d?)(?:\/)?$/i);
        if (m) {
          contestId = m[1];
          problemIndex = m[2];
        }
      }
    }
    
    if (!contestId || !problemIndex) return;
    
    // 生成跳转链接
    const luoguUrl = `https://www.luogu.com.cn/problem/CF${contestId}${problemIndex}`;
    const vjudgeUrl = `https://vjudge.net/problem/CodeForces-${contestId}${problemIndex}`;
    
    // === AUTO REDIRECT(可选)===
    // 如果需要自动跳转到 Luogu,取消下面一行注释:
    // window.location.href = luoguUrl;
    // 如果需要自动跳转到 VJudge,取消下面一行注释:
    // window.location.href = vjudgeUrl;
    
    // 创建两个按钮
    createJumpButton(luoguUrl, 'Jump to Luogu', 'luogu-redirect-btn', '100px');
    createJumpButton(vjudgeUrl, 'Jump to VJudge', 'vj-redirect-btn', '150px');
    return;
  }
  
  // 其他域名不处理
})();