在 CodeChef、QOJ、Codeforces 题目页提供跳转到对应 VJudge/Luogu 题目的按钮(可切换为自动跳转)
// ==UserScript==
// @name CodeChef/QOJ/Codeforces → VJudge/Luogu Redirect
// @namespace http://tampermonkey.net/
// @version 1.3
// @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/105578/problem/E
if (host === 'codeforces.com') {
let contestId = '';
let problemIndex = '';
let isGym = false;
// 形式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];
isGym = true;
}
}
}
if (!contestId || !problemIndex) return;
// 生成跳转链接
// Gym 题目在 VJudge 上格式为 Gym-105578E(无分隔符)
// 普通题目格式为 CodeForces-2120D
const luoguUrl = `https://www.luogu.com.cn/problem/CF${contestId}${problemIndex}`;
const vjudgeUrl = isGym
? `https://vjudge.net/problem/Gym-${contestId}${problemIndex}`
: `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;
}
// 其他域名不处理
})();