您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在 VJudge 比赛题目页添加原题链接
// ==UserScript== // @name VJudge 比赛显示原题链接 // @namespace https://vjudge.net/ // @version 0.1 // @description 在 VJudge 比赛题目页添加原题链接 // @match https://vjudge.net/contest/* // @license MIT // @grant none // @run-at document-end // ==/UserScript== (function () { 'use strict'; const ANCHOR_CONTAINER_ID = 'vjudge-original-link-container'; const DEBOUNCE_MS = 120; let titleObserver = null; let bodyObserver = null; let debounceTimer = null; let lastDataJsonRaw = null; function getProblemLetterFromHash() { const m = location.hash.match(/#problem\/([^/?&]+)/); return m ? m[1] : null; } function parseDataJsonSafely() { const ta = document.querySelector('textarea[name="dataJson"]'); if (!ta) return null; const raw = ta.value || ta.textContent || ta.innerText || ''; if (!raw) return null; if (raw === lastDataJsonRaw) return JSON.parse(raw); try { const parsed = JSON.parse(raw); lastDataJsonRaw = raw; return parsed; } catch (e) { console.warn('vjudge-original-link: JSON parse failed', e); return null; } } function simpleObjHash(obj) { try { return String(JSON.stringify(obj)); } catch (e) { return String(obj); } } function ensureLinkContainerForProblem(problem, letter) { const titleEl = document.getElementById('prob-title-contest'); if (!titleEl) return; const existing = document.getElementById(ANCHOR_CONTAINER_ID); const targetHash = problem ? simpleObjHash({ oj: problem.oj, pid: problem.pid, num: problem.num, title: problem.title }) : ''; const wantLetter = letter || ''; if (existing) { if (existing.dataset.letter === wantLetter && existing.dataset.targetHash === targetHash) { return; // nothing to do } existing.remove(); } if (!problem) return; const container = document.createElement('div'); container.id = ANCHOR_CONTAINER_ID; container.style.marginTop = '6px'; container.style.fontSize = '14px'; container.style.lineHeight = '1.4'; container.dataset.letter = wantLetter; container.dataset.targetHash = targetHash; const oj = (problem.oj || '').toString().toLowerCase(); const title = problem.title || ''; let a = document.createElement('a'); a.style.textDecoration = 'none'; a.style.fontWeight = '500'; a.target = '_blank'; a.rel = 'noopener noreferrer'; if (oj.includes('luogu') || oj.includes('洛谷') || oj.includes('luogu.com')) { const probNum = problem.probNum || problem.probID || problem.pid || null; if (probNum) { a.href = `https://www.luogu.com.cn/problem/${encodeURIComponent(probNum)}`; a.textContent = `原题:洛谷 ${probNum}`; } else { a.removeAttribute('href'); a.textContent = '原题:暂不支持'; a.style.cursor = 'default'; } } else if (oj.includes('qoj')) { const q = encodeURIComponent(title || ''); a.href = `https://qoj.ac/problems?search=${q}`; a.textContent = '原题:QOJ(点击搜索)'; } else if (oj.includes('codeforces')) { const q = encodeURIComponent(title || ''); a.href = `https://www.luogu.com.cn/problem/list?keyword=${q}&type=CF&page=1`; a.textContent = '原题:Codeforces(点击搜索)'; } else if (oj.includes('atcoder')) { const probNum = problem.probNum || problem.probID || problem.pid || null; const contestId = probNum.slice(0, probNum.lastIndexOf("_")); a.href = `https://atcoder.jp/contests/${contestId}/tasks/${probNum}`; a.textContent = `原题:AtCoder ${probNum}`; } else if (oj.includes('uva')) { const q = encodeURIComponent(title || ''); a.href = `https://www.luogu.com.cn/problem/list?keyword=${q}&type=UVA&page=1`; a.textContent = '原题:UVA(点击搜索)'; } else if (oj.includes('universaloj')) { const q = encodeURIComponent(title || ''); a.href = `https://uoj.ac/problems?search=${q}`; a.textContent = '原题:UOJ(点击搜索)'; } else if (oj.includes('libreoj')) { const q = encodeURIComponent(title || ''); a.href = `https://loj.ac/p?keyword=${q}`; a.textContent = '原题:LOJ(点击搜索)'; } else { a.removeAttribute('href'); a.textContent = `原题:来自 ${oj},暂不支持查找`; a.style.cursor = 'default'; } container.appendChild(a); try { titleEl.appendChild(container); } catch (e) { console.warn('vjudge-original-link: insert failed', e); } } // 主要更新逻辑(防抖) function handleUpdate() { clearTimeout(debounceTimer); debounceTimer = setTimeout(() => { try { const letter = getProblemLetterFromHash(); if (!letter) { const ex = document.getElementById(ANCHOR_CONTAINER_ID); if (ex) ex.remove(); return; } const data = parseDataJsonSafely(); if (!data || !Array.isArray(data.problems)) { const ex2 = document.getElementById(ANCHOR_CONTAINER_ID); if (ex2) ex2.remove(); return; } const target = data.problems.find(p => { if (!p) return false; if (p.num && String(p.num).toUpperCase() === String(letter).toUpperCase()) return true; if (p.num && String(p.num).toUpperCase().includes(String(letter).toUpperCase())) return true; return false; }) || null; ensureLinkContainerForProblem(target, letter); } catch (err) { console.error('vjudge-original-link: handleUpdate error', err); } }, DEBOUNCE_MS); } function startTitleObserver() { stopTitleObserver(); const titleEl = document.getElementById('prob-title-contest'); if (!titleEl) return; titleObserver = new MutationObserver((mutations) => { for (const m of mutations) { if (m.addedNodes) { for (const n of m.addedNodes) { if (n && n.id === ANCHOR_CONTAINER_ID) { return; } if (n && n.querySelector && n.querySelector(`#${ANCHOR_CONTAINER_ID}`)) { return; } } } } handleUpdate(); }); titleObserver.observe(titleEl, { childList: true, subtree: true, characterData: true }); } function stopTitleObserver() { if (titleObserver) { try { titleObserver.disconnect(); } catch (e) {} titleObserver = null; } } function startBodyObserver() { if (bodyObserver) return; bodyObserver = new MutationObserver((mutations) => { for (const m of mutations) { if (m.addedNodes) { for (const n of m.addedNodes) { if (n && n.querySelector && n.querySelector('#prob-title-contest')) { setTimeout(() => { startTitleObserver(); handleUpdate(); }, 20); return; } if (n && n.id === 'prob-title-contest') { setTimeout(() => { startTitleObserver(); handleUpdate(); }, 20); return; } } } } }); bodyObserver.observe(document.body, { childList: true, subtree: true }); } function stopBodyObserver() { if (bodyObserver) { try { bodyObserver.disconnect(); } catch (e) {} bodyObserver = null; } } function tryInit(retries = 10) { const titleEl = document.getElementById('prob-title-contest'); if (titleEl) { startTitleObserver(); startBodyObserver(); handleUpdate(); } else if (retries > 0) { setTimeout(() => tryInit(retries - 1), 300); } else { startBodyObserver(); } } window.addEventListener('hashchange', () => { setTimeout(handleUpdate, 80); }); // run tryInit(); window.__vjudge_original_link_cleanup = function () { stopTitleObserver(); stopBodyObserver(); const ex = document.getElementById(ANCHOR_CONTAINER_ID); if (ex) ex.remove(); console.info('vjudge-original-link: cleaned up'); }; })();