// ==UserScript== // @name GenGen-RMJ // @icon https://cdn.luogu.com.cn/upload/image_hosting/yy27itbl.png // @namespace https://gengen.qzz.io/ // @version 5.0-dev // @description GenGen RMJ // @author GenGen 队 // @run-at document-start // @match https://challenges.cloudflare.com/cdn-cgi/challenge-platform/* // @match https://www.luogu.com.cn/* // @match https://atcoder.jp/contests/*/submit?RMJ=1 // @match https://codeforces.com/* // @require https://scriptcat.org/scripts/code/7193/GenGen-RMJ-JL.user.js // @require https://scriptcat.org/scripts/code/5096/GenGen-Cloudflare-RMJ.user.js // @require https://scriptcat.org/scripts/code/7286/GenGen-RMJ-DET.user.js // @connect * // @inject-into page // @grant GM_addStyle // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant unsafeWindow // @license MIT // ==/UserScript== // (() => { 'use strict'; if (location.hostname === 'challenges.cloudflare.com') { const XHR = (unsafeWindow || window).XMLHttpRequest; if (XHR) { const [o, s] = [XHR.prototype.open, XHR.prototype.send]; XHR.prototype.open = function(m, u) { this._u = u || ''; return o.apply(this, arguments); }; XHR.prototype.send = function(b) { if (this._u) this.timeout = undefined; return s.apply(this, arguments); }; } } const V = '5.0-dev', P = 1000, T = `[RMJ ${V}]`; const C = { ac: '#52c41a', wa: '#e74c3c', na: '#404040', p: '#0ea5e9', bd: '#e2e8f0', t: '#1e293b', m: '#64748b', bg: '#fff', alt: '#f8fafc' }; const L = (...a) => console.log(T, ...a); const E = (...a) => console.error(T, ...a); const R = fn => document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', fn) : fn(); const W = (sel, cb, ms = 12000) => { const t0 = Date.now(); const iv = setInterval(() => { const el = document.querySelector(sel); if (el) { clearInterval(iv); cb(el); } else if (Date.now() - t0 > ms) clearInterval(iv); }, 120); }; const isL = () => location.hostname === 'www.luogu.com.cn'; const isCF = () => location.hostname === 'codeforces.com'; const SVG = { ok: ``, err: ``, na: `` }; GM_addStyle(` .rmj-trigger{display:inline-flex;align-items:center;gap:8px;padding:8px 16px;background:#fff;color:${C.t};border:1px solid ${C.bd};border-radius:8px;font:600 13px 'JetBrains Mono',monospace;cursor:pointer;transition:.2s;box-shadow:0 1px 3px rgba(0,0,0,.04);user-select:none;margin-right:10px;height:36px} .rmj-trigger:hover{border-color:${C.p};color:${C.p};box-shadow:0 2px 8px rgba(14,165,233,.15);transform:translateY(-1px)} .rmj-trigger img{width:20px;height:20px;border-radius:4px} .rmj-overlay{pointer-events: auto; position:fixed;inset:0;background:rgba(0,0,0,.4);z-index:2147483646;display:flex;align-items:center;justify-content:center;opacity:0;visibility:hidden;transition:.25s} .rmj-overlay.show{opacity:1;visibility:visible} .rmj-modal{width:480px;max-height:85vh;background:rgba(255, 255, 255, 0.8);backdrop-filter:blur(6px);border-radius:6px;box-shadow:0 25px 60px rgba(0,0,0,.12);display:flex;flex-direction:column;overflow:hidden;transform:scale(.95) translateY(10px);transition:transform .3s cubic-bezier(.34,1.56,.64,1), height .3s ease} .rmj-pages{flex:1;overflow:hidden;position:relative;min-height:0;height:auto;transition:height .3s ease} .rmj-page:not(.active){visibility:hidden;pointer-events:none;position:absolute;top:0;left:50%;width:50%;height:100%} .rmj-page.active { visibility: visible; pointer-events: auto; opacity: 1; z-index: 1; } .rmj-overlay.show .rmj-modal{transform:scale(1) translateY(0)} .rmj-head{padding:20px 24px 16px;border-bottom:1px solid ${C.bd};display:flex;align-items:center;gap:12px} .rmj-head img{width:36px;height:36px;border-radius:8px} .rmj-title{flex:1;margin:0;font:700 20px 'JetBrains Mono',monospace;background:linear-gradient(135deg,${C.p},#2563eb);-webkit-background-clip:text;-webkit-text-fill-color:transparent} .rmj-close{width:32px;height:32px;border:0;background:0 0;color:${C.m};cursor:pointer;border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:20px;transition:.15s} .rmj-close:hover{background:${C.alt};color:${C.t}} .rmj-pages { display: grid; width: 100%; overflow: hidden; transition: height .3s ease; } .rmj-pages-inner { display: contents; } .rmj-page { grid-area: 1 / 1 / 2 / 2; padding: 24px; overflow-y: auto; box-sizing: border-box; visibility: hidden; pointer-events: none; opacity: 0; transition: opacity .3s ease, visibility .3s; } .rmj-welcome-title{font-size:18px;font-weight:700;color:${C.t};margin-bottom:12px} .rmj-update-badge{display:inline-block;padding:3px 10px;background:linear-gradient(135deg,${C.p},#2563eb);color:#fff;border-radius:20px;font-size:11px;font-weight:600;margin-left:8px} .rmj-feat{list-style:none;padding:0;margin:16px 0} .rmj-feat li{display:flex;align-items:flex-start;gap:10px;padding:8px 0;color:${C.m};font-size:14px;line-height:1.5} .rmj-feat li::before{content:'·';flex-shrink:0} .rmj-sec{font-size:11px;font-weight:700;color:${C.m};text-transform:uppercase;letter-spacing:1px;margin:0 0 12px} .rmj-sec:not(:first-child){margin-top:20px} .rmj-card{background:${C.alt};border:1px solid ${C.bd};border-radius:10px;padding:14px 16px;display:flex;align-items:center;justify-content:space-between;transition:.2s} .rmj-card:hover{border-color:${C.p}33;box-shadow:0 2px 8px rgba(14,165,233,.08)} .rmj-card+.rmj-card{margin-top:8px} .rmj-info{display:flex;align-items:center;gap:12px} .rmj-logo{width:32px;height:32px;border-radius:6px;object-fit:contain} .rmj-name{font-size:14px;font-weight:600;color:${C.t}} .rmj-badge{display:inline-flex;align-items:center;gap:4px;padding:4px 12px;border-radius:20px;font-size:12px;font-weight:600} .rmj-badge.on{background:${C.ac}15;color:${C.ac}} .rmj-badge.on::before{content:'';width:6px;height:6px;border-radius:50%;background:${C.ac};animation:pulse 2s infinite} .rmj-badge.off{background:${C.wa}15;color:${C.wa};cursor:pointer;text-decoration:none;transition:.15s} .rmj-badge.off:hover{background:${C.wa}25;transform:translateY(-1px)} .rmj-badge.load{background:#f1f5f9;color:${C.m}} @keyframes pulse{0%,100%{opacity:1}50%{opacity:.5}} .rmj-spin{width:14px;height:14px;border:2px solid ${C.bd};border-top-color:${C.p};border-radius:50%;animation:spin .8s linear infinite} @keyframes spin{to{transform:rotate(360deg)}} .rmj-links{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:12px} .rmj-link{display:flex;align-items:center;gap:8px;padding:10px 12px;background:${C.alt};border:1px solid ${C.bd};border-radius:8px;color:${C.t};text-decoration:none;font-size:13px;font-weight:500;transition:.2s} .rmj-link:hover{border-color:${C.p};color:${C.p};transform:translateY(-1px);box-shadow:0 2px 8px rgba(14,165,233,.1)} .rmj-link svg{width:16px;height:16px;flex-shrink:0} .rmj-foot{padding:16px 24px;border-top:1px solid ${C.bd};display:flex;align-items:center;justify-content:space-between;background:${C.alt}} .rmj-btn{padding:8px 16px;border:1px solid ${C.bd};background:#fff;color:${C.t};border-radius:6px;font-size:13px;font-weight:500;cursor:pointer;transition:.15s} .rmj-btn:hover:not(:disabled){border-color:${C.p};color:${C.p}} .rmj-btn:disabled{opacity:.4;cursor:not-allowed} .rmj-dots{display:flex;gap:6px} .rmj-dot{width:6px;height:6px;border-radius:50%;background:${C.bd};transition:.2s} .rmj-dot.active{background:${C.p};width:18px;border-radius:3px} .rmj-prob{display: inline-flex;align-items: center;transition: all .3s;} .rmj-prob.ac{color:${C.ac};font-weight:bold} .rmj-prob.wa{color:${C.wa};font-weight:bold} .rmj-prob.na{color:#3498db;} .rmj-ico{position:absolute;inset:0;background:0 0;display:flex;align-items:center;justify-content:center;z-index:10;border-radius:inherit;animation:fadeIn .3s} .rmj-ico svg{width:18px;height:18px;animation:popIn .3s cubic-bezier(.34,1.56,.64,1)} .rmj-hide>*:not(.rmj-ico){visibility:hidden!important} @keyframes fadeIn{from{opacity:0}to{opacity:1}} @keyframes popIn{from{transform:scale(.5);opacity:0}to{transform:scale(1);opacity:1}} @keyframes badgePop{0%{transform:scale(.8);opacity:0}60%{transform:scale(1.05)}100%{transform:scale(1);opacity:1}} .rmj-record-badge{animation:badgePop .4s ease-out} .rmj-sel{position:relative;display:inline-block;margin-left:8px} .rmj-sel-btn{position:relative;display:inline-flex;align-items:center;min-width:120px;padding:.3125em 1.5em .3125em 1em;border:1px solid #ccc;border-radius:3px;background:#fff;font-size:14px;line-height:1.4;cursor:pointer;text-align:left} .rmj-sel-btn::after{content:"";position:absolute;right:10px;top:50%;width:6px;height:6px;border-right:1.5px solid #555;border-bottom:1.5px solid #555;transform:translateY(-65%) rotate(45deg);transition:.2s} .rmj-sel.open .rmj-sel-btn::after{transform:translateY(-35%) rotate(-135deg)} .rmj-sel-menu{position:absolute;top:calc(100% + 4px);left:0;width:100%;margin:0;padding:4px 0;list-style:none;background:#fff;border:1px solid #ccc;border-radius:4px;box-shadow:0 4px 12px rgba(0,0,0,.1);opacity:0;transform:translateY(-8px);pointer-events:none;transition:.2s;z-index:100} .rmj-sel-menu.open{opacity:1;transform:translateY(0);pointer-events:all} .rmj-sel-menu li{padding:3px 12px;cursor:pointer;font-size:14px} .rmj-sel-menu li:hover{background:#f0f9ff} .rmj-refresh-wrap{display:inline-flex;align-items:center;margin-left:8px;cursor:pointer;color:${C.m};transition:.2s} .rmj-refresh-wrap:hover{color:${C.p};transform:rotate(90deg)} .rmj-refresh{width:14px;height:14px} .rmj-switch{position:relative;display:inline-block;width:40px;height:22px} .rmj-switch input{opacity:0;width:0;height:0} .rmj-slider{position:absolute;cursor:pointer;inset:0;background:#ccc;transition:.3s;border-radius:22px} .rmj-slider:before{position:absolute;content:"";height:16px;width:16px;left:3px;bottom:3px;background:#fff;transition:.3s;border-radius:50%} .rmj-switch input:checked + .rmj-slider{background:${C.p}} .rmj-switch input:checked + .rmj-slider:before{transform:translateX(18px)} .rmj-switch input:disabled + .rmj-slider{opacity:.5;cursor:not-allowed} `); let cfM = new Map(), atM = new Map(), cfR = '', atR = ''; let cr = false, lT = null, pT = null, md = null, pg = 0; let cfPollTimer = null; const SPECIAL_ACCOUNTS = ['General0000000826', 'General000000826']; const isSpecialAccount = () => { const cfH = GM_getValue('cf-handle', ''); const atH = GM_getValue('at-handle', ''); return SPECIAL_ACCOUNTS.includes(cfH) || SPECIAL_ACCOUNTS.includes(atH); }; const getDisableSync = () => { if (isSpecialAccount()) return true; let pVal = false; try { const params = new URLSearchParams(location.search); pVal = params.get('rmj-disable-sync') === 'true'; } catch(e) {} if (pVal) return true; // 确保 GM 中始终有明确的值,首次读取时写入默认值 false(默认启用同步) if (GM_getValue('rmj-disable-sync', null) === null) { GM_setValue('rmj-disable-sync', false); } return GM_getValue('rmj-disable-sync', false) === true; }; const capCF = () => { if (!isCF()) return; const try_ = () => { const el = document.querySelector('#header > div.lang-chooser > div:nth-child(2) > a:nth-child(1)'); if (!el) return setTimeout(try_, 1000); const t = el.textContent.trim(); if (t && !['Enter', '登录'].includes(t)) { const o = GM_getValue('cf-handle', ''); if (o !== t) GM_setValue('cf-handle', t); } }; try_(); }; const capAT = () => { cr = true; uM(); GM_xmlhttpRequest({ method: 'GET', url: 'https://atcoder.jp/', timeout: 8000, onload: r => { cr = false; if (r.status !== 200) return uM(); try { const doc = new DOMParser().parseFromString(r.responseText, 'text/html'); const el = doc.querySelector('#navbar-collapse > ul.nav.navbar-nav.navbar-right > li:nth-child(2) > a'); if (!el || el.textContent.trim() === 'Sign Up') return uM(); const h = el.textContent.trim(); const o = GM_getValue('at-handle', ''); if (o !== h) GM_setValue('at-handle', h); uM(); } catch (e) { E(e); } }, onerror: () => { cr = false; uM(); } }); }; const rC = () => { try { const cR = GM_getValue('cf-submissions-cache', '[]'); const aR = GM_getValue('at-submissions-cache', '[]'); if (cR !== cfR) { cfR = cR; cfM.clear(); JSON.parse(cR).forEach(s => { const m = s?.taskDisplay?.match(/CF[A-Z0-9]+/i); if (m) { const k = m[0].toUpperCase(), v = s.status || null; if (!cfM.has(k) || (cfM.get(k) !== 'OK' && v === 'OK')) cfM.set(k, v); } }); } if (aR !== atR) { atR = aR; atM.clear(); JSON.parse(aR).forEach(s => { if (s?.taskKey) { const k = s.taskKey.toLowerCase(), v = s.status || null; if (!atM.has(k) || (atM.get(k) !== 'AC' && v === 'AC')) atM.set(k, v); } }); } } catch (e) { E(e); } }; const qS = pid => { if (/^CF[A-Z0-9]+$/i.test(pid)) { const v = cfM.get(pid.toUpperCase()); return v === 'OK' ? 'AC' : v ? 'WA' : 'NONE'; } if (/^AT_/i.test(pid)) { const k = pid.replace(/^AT_/i, '').toLowerCase(); const v = atM.get(k); return v === 'AC' ? 'AC' : v ? 'WA' : 'NONE'; } return null; }; const uL = () => { rC(); document.querySelectorAll('.row').forEach(row => { const [sc, pc] = row.children; if (!pc) return; const m = pc.textContent.trim().match(/^(\S+)/); if (!m) return; const st = qS(m[1]); if (!st) { const ov = sc.querySelector('.rmj-ico'); if (ov) { ov.remove(); sc.classList.remove('rmj-hide'); } return; } let ov = sc.querySelector('.rmj-ico'); if (!ov) { ov = document.createElement('div'); ov.className = 'rmj-ico'; sc.style.position = 'relative'; sc.appendChild(ov); sc.classList.add('rmj-hide'); } const icon = st === 'AC' ? SVG.ok : st === 'WA' ? SVG.err : SVG.na; if (ov.dataset.s !== st) { ov.innerHTML = icon; ov.dataset.s = st; } }); }; const sL = () => { if (!lT) { uL(); lT = setInterval(uL, P); } }; const xL = () => { if (lT) { clearInterval(lT); lT = null; } }; const uP = () => { rC(); const pid = location.pathname.split('/problem/')[1]; if (!pid) return; const st = qS(pid); if (!st) return; const link = document.querySelectorAll('a[href*="/record/list?pid="]')[0]; if (!link) return; let tag = link.querySelector('.rmj-prob'); if (!tag) { const orig = link.querySelector('span'); if (orig) orig.style.display = 'none'; tag = document.createElement('span'); tag.className = 'rmj-prob'; link.appendChild(tag); } const way = document.querySelectorAll('a[href*="https://help.luogu.com.cn/manual/luogu/problem/remote-judge"]')[0]; if(way && way.textContent!="GenGen RMJ"){ console.log(way); way.textContent="GenGen RMJ"; way.href="https://gengen.qzz.io/rmj/"; } const cls = st === 'AC' ? 'ac' : st === 'WA' ? 'wa' : 'na'; const txt = st === 'AC' ? 'Accepted' : st === 'WA' ? 'Unaccepted' : '暂无'; if (tag.dataset.s !== st) { tag.className = 'rmj-prob ' + cls; tag.innerHTML = txt; tag.dataset.s = st; } }; const sP = () => { if (!pT) { uP(); pT = setInterval(uP, P); } }; const xP = () => { if (pT) { clearInterval(pT); pT = null; } }; const startCfPoll = () => { if (cfPollTimer) clearInterval(cfPollTimer); const initialHandle = GM_getValue('cf-handle', ''); let checks = 0; cfPollTimer = setInterval(() => { checks++; const currentHandle = GM_getValue('cf-handle', ''); if (currentHandle !== initialHandle || checks > 60) { clearInterval(cfPollTimer); cfPollTimer = null; uM(); } }, 1000); uM(); }; const bM = () => { if (md) md.remove(); md = document.createElement('div'); md.className = 'rmj-overlay'; md.innerHTML = `
GenGen RMJ`;
btn.onclick = () => shM(1);
nav.parentElement.insertBefore(btn, nav);
});
};
const hP = () => {
const p = location.pathname;
if (!p.startsWith('/problem/CF') && !p.startsWith('/problem/AT')) return;
const rmj = p.startsWith('/problem/CF') ? '1' : '2';
const pid = p.split('/problem/')[1];
if (!pid) return;
W('.side', side => {
new MutationObserver(() => {
side.querySelectorAll('a[href*="/record/list?pid="]').forEach(link => {
if (link.dataset.rmjHook) return;
link.dataset.rmjHook = '1';
link.addEventListener('click', e => {
if (e.button !== 0) return;
e.preventDefault();
e.stopImmediatePropagation();
const u = new URL('/record/list', location.origin);
const sp = new URLSearchParams(location.search);
for (const [k, v] of sp) u.searchParams.set(k, v);
u.searchParams.set('pid', pid);
u.searchParams.set('rmj', rmj);
location.href = u.toString();
}, true);
link.addEventListener('mousedown', e => {
if (e.button !== 1) return;
const u = new URL('/record/list', location.origin);
const sp = new URLSearchParams(location.search);
for (const [k, v] of sp) u.searchParams.set(k, v);
u.searchParams.set('pid', pid);
u.searchParams.set('rmj', rmj);
link.href = u.toString();
}, true);
});
uP();
}).observe(side, { childList: true, subtree: true });
});
uP();sP();
};
const iS = () => {
if (!location.pathname.startsWith('/record/list')) return;
W('#app > div.main-container.lside-nav > div > main > div > div:nth-child(1) > section:nth-child(2) > div > div > div.text.lform-size-middle', b => {
if (b.dataset.rmj) return;
b.dataset.rmj = '1';
const w = document.createElement('span');
w.className = 'rmj-sel';
w.innerHTML = `
`;
b.after(w);
const btn = w.querySelector('.rmj-sel-btn');
const menu = w.querySelector('.rmj-sel-menu');
btn.addEventListener('click', e => {
e.stopPropagation();
w.classList.toggle('open');
menu.classList.toggle('open');
});
menu.querySelectorAll('li').forEach(li => {
li.addEventListener('click', e => {
e.stopPropagation();
const val = li.dataset.value;
btn.textContent = li.textContent;
w.classList.remove('open');
menu.classList.remove('open');
const p = new URLSearchParams(location.search);
val ? p.set('rmj', val) : p.delete('rmj');
location.search = p.toString();
});
});
document.addEventListener('click', () => {
w.classList.remove('open');
menu.classList.remove('open');
});
const cur = new URLSearchParams(location.search).get('rmj') || '';
const act = menu.querySelector(`li[data-value="${cur}"]`);
if (act) btn.textContent = act.textContent;
});
};
let prev = location.pathname;
const onR = () => {
const cur = location.pathname;
if (cur === prev) return;
const wasL = prev.startsWith('/training') || prev.startsWith('/problem/list');
const nowL = cur.startsWith('/training') || cur.startsWith('/problem/list');
const wasP = prev.startsWith('/problem/CF') || prev.startsWith('/problem/AT');
const nowP = cur.startsWith('/problem/CF') || cur.startsWith('/problem/AT');
if (wasL && !nowL) xL();
if (!wasL && nowL) sL();
if (wasP && !nowP) xP();
if (!wasP && nowP) hP();
prev = cur;
};
const main = () => {
if (isCF()) { capCF(); return; }
if (!isL()) return;
chV();
iB();
hP();
iS();
if (location.pathname.startsWith('/training') || location.pathname.startsWith('/problem/list')) sL();
setInterval(onR, 200);
addEventListener('beforeunload', () => { xL(); xP(); });
};
R(main);
})();