// ==UserScript== // @name GenGen-RMJ // @icon https://gengen.qzz.io/favicon.ico // @namespace https://gengen.qzz.io/ // @version 3.1.3.1 // @description GenGen RMJ 完整版本 // @author GenGen 队 // @run-at document-start // @match https://www.luogu.com.cn/* // @match https://atcoder.jp/contests/*/submit?RMJ=1 // @match https://codeforces.com/problemset/submit/* // @match https://codeforces.com/problemset/status?my=on // @require https://cdn.bootcdn.net/ajax/libs/html2canvas/1.4.1/html2canvas.js // @require https://cdn.bootcdn.net/ajax/libs/sweetalert2/11.23.0/sweetalert2.all.js // @require https://scriptcat.org/scripts/code/5095/GenGen-CF-RMJ-JL.user.js // @require https://scriptcat.org/scripts/code/5096/GenGen-Cloudflare-RMJ.user.js // @require https://scriptcat.org/scripts/code/5093/GenGen-AT-RMJ-JL.user.js // @require https://scriptcat.org/scripts/code/5094/GenGen-CF-RMJ.user.js // @grant GM_addStyle // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant unsafeWindow // ==/UserScript== (function () { 'use strict'; const V = '3.1.3.1'; const D = '2026/2/8'; const I = 500; // SPA cleanup state let c = false; let u = location.href; // —————— 工具 —————— const r = fn => document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', fn) : fn(); const w = (s, cb) => { const t = setInterval(() => { const e = document.querySelector(s); if (e) { clearInterval(t); cb(e); } }, 100); }; // —————— 样式 —————— GM_addStyle(` .ghb { background: linear-gradient(120deg, #4da6ff, #2196f3); color: white !important; border: none; border-radius: 6px; font-family: "Console", monospace; padding: 4px 12px; font-weight: bold; font-style: normal; font-size: 16px; cursor: pointer; margin-right: 12px; box-shadow: 0 2px 6px rgba(0, 67, 133, 0.25); transition: all 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275); height: 30px; display: inline-flex; align-items: center; justify-content: center; letter-spacing: 0.5px; } .ghb:hover { transform: translateY(-1px); box-shadow: 0 4px 8px rgba(0, 67, 133, 0.35); background: linear-gradient(120deg, #3d8be6, #1976d2); } .ghb:active { transform: translateY(0); } #gp { position: fixed; width: 400px; backdrop-filter: blur(4px); border: 1px solid rgba(0, 0, 0, 0.08); border-radius: 5px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.18); z-index: 2147483647; padding: 20px; font-size: 16px; display: flex; flex-direction: column; gap: 14px; opacity: 0; visibility: hidden; transform: translateY(-8px); transition: opacity 0.32s, transform 0.36s, visibility 0.32s; pointer-events: none; } #gp.v { opacity: 1; visibility: visible; transform: translateY(0); pointer-events: all; } #gp h3 { margin: 0 0 12px 0; font-size: 34px; font-weight: bold; font-style: normal; font-family: "Courier New", serif; color: #1a237e; display: flex; align-items: center; justify-content: center; gap: 10px; border-bottom: 1.5px solid #e8eaf6; padding-bottom: 10px; letter-spacing: -0.5px; } #gp h3 img { width: 30px; height: 30px; border-radius: 6px; flex-shrink: 0; } #gp p { margin: 0; line-height: 1.6; color: #37474f; } #gp a { color: #1565c0; text-decoration: none; font-weight: 500; } #gp a:hover { text-decoration: underline; } .ar { display: flex; justify-content: space-between; padding: 6px 0; } .as { font-weight: 600; color: #2e7d32; } .nb { color: #c62828; font-weight: 500; } .bb { font-size: 13px; padding: 3px 8px; background: #e3f2fd; border: 1px solid #1e88e5; border-radius: 16px; cursor: pointer; color: #0d47a1; font-weight: 500; transition: all 0.2s; } .bb:hover { background: #bbdefb; transform: scale(1.05); } .sw { display: none; justify-content: center; align-items: center; min-height: 24px; width: 100%; } .m .oc { display: none !important; } .m .sw { display: flex !important; } `); // —————— 图标 —————— const ok = () => ``; const er = () => ``; const no = () => ``; // —————— 缓存 —————— let cf = new Map(), at = new Map(), lcf = '', lat = ''; function rc() { try { const a = GM_getValue('cf-submissions-cache', '[]'); const b = GM_getValue('at-submissions-cache', '[]'); if (a !== lcf) { lcf = a; cf = new Map(); if (a && a !== '[]') JSON.parse(a).forEach(s => { if (!s?.taskDisplay) return; const m = s.taskDisplay.match(/CF[A-Z0-9]+/i); if (m) cf.set(m[0].toUpperCase(), s.verdict || null); }); } if (b !== lat) { lat = b; at = new Map(); if (b && b !== '[]') JSON.parse(b).forEach(s => { if (s?.taskKey) at.set(s.taskKey.toLowerCase(), s.status || null); }); } } catch (e) {} } // —————— DOM 初始化(仅 CF/AT) —————— function ic(e) { if (e.dataset.g) return; const o = document.createElement('div'); o.className = 'oc'; while (e.firstChild) o.appendChild(e.firstChild); const s = document.createElement('div'); s.className = 'sw'; s.innerHTML = no(); e.appendChild(o); e.appendChild(s); e.dataset.g = '1'; } // —————— 更新行状态(安全版) —————— function ur(rw) { const cs = rw.children; if (cs.length < 2) return false; const c0 = cs[0], c1 = cs[1]; const m = c1.textContent.trim().match(/^(\S+)/); if (!m) return false; const p = m[1]; const isCF = /^CF[A-Z0-9]+$/i.test(p); const isAT = /^AT_[A-Za-z0-9_]+$/.test(p); const man = isCF || isAT; // —————— 非管辖题目:绝不碰 DOM —————— if (!man) { // 如果之前错误初始化了(比如开发残留),清理一次 if (c0.dataset.g) { const o = c0.querySelector('.oc'); if (o) { c0.innerHTML = o.innerHTML; // 恢复原始内容 delete c0.dataset.g; delete c0.dataset.s; } } return false; } // —————— 管辖题目:安全初始化 + 更新 —————— if (!c0.dataset.g) { // 初始化容器(仅移动节点,不 innerHTML) const o = document.createElement('div'); o.className = 'oc'; while (c0.firstChild) o.appendChild(c0.firstChild); const s = document.createElement('div'); s.className = 'sw'; s.innerHTML = no(); c0.appendChild(o); c0.appendChild(s); c0.dataset.g = '1'; } let ns = 'none', icn = no(); if (isCF) { const v = cf.get(p.toUpperCase()); if (v === 'OK') { icn = ok(); ns = 'AC'; } else if (v) { icn = er(); ns = 'WA'; } } else if (isAT) { const k = p.substring(3).toLowerCase(); const st = at.get(k); if (st === 'AC') { icn = ok(); ns = 'AC'; } else if (st) { icn = er(); ns = 'WA'; } } const os = c0.dataset.s || 'none'; const sw = c0.querySelector('.sw'); if (sw) sw.innerHTML = icn; c0.classList.add('m'); c0.dataset.s = ns; return os !== ns; } // —————— 清理函数(离开 CF/AT 时调用) —————— function cl() { document.querySelectorAll('[data-g="1"]').forEach(e => { const o = e.querySelector('.oc'); if (o) { while (o.firstChild) e.appendChild(o.firstChild); e.removeChild(o); const s = e.querySelector('.sw'); if (s) e.removeChild(s); delete e.dataset.g; delete e.dataset.s; e.classList.remove('m'); } }); } let curURL = location.href; function onNav() { const newURL = location.href; if (newURL === curURL) return; curURL = newURL; // 从 CF/AT 单题页离开? const wasCFAT = /^\/problem\/(CF|AT_)/.test(new URL.split('?')[0].replace(location.origin, '')); const nowCFAT = /^\/problem\/(CF|AT_)/.test(location.pathname); if (wasCFAT && !nowCFAT) { // 清理所有被 GenGen 修改的元素 document.querySelectorAll('[data-g="1"]').forEach(e => { const o = e.querySelector('.oc'); if (o) { e.innerHTML = o.innerHTML; delete e.dataset.g; delete e.dataset.s; } }); } } // —————— 循环更新 —————— let tid = null; function st() { if (tid) clearInterval(tid); tid = setInterval(() => { rc(); document.querySelectorAll('.row').forEach(ur); }, I); } function sp() { if (tid) { clearInterval(tid); tid = null; } } // —————— 面板 —————— let pn = null, ht = null; const sd = 200, hd = 300; function cp() { if (pn) return; pn = document.createElement('div'); pn.id = 'gp'; pn.innerHTML = `

GenGen GenGen RMJ 3.1

版本:${V} (${D})

官网:gengen.qzz.io

反馈:私信开发者

新特性:支持 CodeForces / AtCoder

工单进度:点击查看

Codeforces 账号: 加载中…
AtCoder 账号: 加载中…
`; document.body.appendChild(pn); cfh(handle => { const el = document.getElementById('cf-s'); if (handle) el.innerHTML = `${handle}`; else el.innerHTML = `未绑定 登录`; }); ath().then(handle => { const el = document.getElementById('at-s'); if (handle) el.innerHTML = `${handle}`; else el.innerHTML = `未绑定 登录`; }); pn.addEventListener('mouseenter', () => clearTimeout(ht)); pn.addEventListener('mouseleave', () => ht = setTimeout(() => pn?.classList.remove('v'), hd)); } function pp(r) { if (!pn) return; let t = r.bottom + 8, l = r.left; if (l + 400 > innerWidth - 16) l = innerWidth - 400 - 16; if (t + 300 > innerHeight) t = r.top - 320; if (t < 16) t = 16; pn.style.top = `${t}px`; pn.style.left = `${l}px`; } function spn(r) { clearTimeout(ht); if (!pn) cp(); pp(r); pn.classList.add('v'); } // —————— 获取账号 —————— async function ath() { return new Promise(r => { GM_xmlhttpRequest({ method: 'GET', url: 'https://atcoder.jp/', onload: res => { if (res.status !== 200) return r(null); try { const d = new DOMParser().parseFromString(res.responseText, 'text/html'); const u = d.querySelector("#navbar-collapse > ul.nav.navbar-nav.navbar-right > li:nth-child(2) > a"); if (!u || u.textContent.trim() === 'Sign Up') r(null); else r(u.textContent.trim()); } catch { r(null); } }, onerror: () => r(null), timeout: 8000 }); }); } function cfh(cb) { GM_xmlhttpRequest({ method: 'GET', url: 'https://codeforces.com/', withCredentials: true, onload: res => { if (res.status !== 200) return cb(null); try { const d = new DOMParser().parseFromString(res.responseText, 'text/html'); const l = d.querySelector('#header > div.lang-chooser > div:nth-child(2) > a:nth-child(1)'); if (!l) return cb(null); const t = l.textContent.trim(); if (t === 'Enter') cb(null); else cb(t); } catch { cb(null); } }, onerror: () => cb(null), timeout: 8000 }); } // —————— 按钮注入 —————— function ih() { if (location.pathname !== '/' || document.querySelector('.ghb')) return; w('.user-nav', n => { if (n.dataset.p) return; n.dataset.p = '1'; const b = document.createElement('pre'); b.className = 'ghb'; b.setAttribute('aria-label', 'GenGen RMJ 工具面板'); b.innerHTML = `GenGen GenGen RMJ`; let stmr = null; b.addEventListener('mouseenter', () => { clearTimeout(stmr); stmr = setTimeout(() => spn(b.getBoundingClientRect()), sd); }); b.addEventListener('mouseleave', () => { clearTimeout(stmr); ht = setTimeout(() => pn?.classList.remove('v'), hd); }); n.parentElement.insertBefore(b, n); addEventListener('resize', () => { if (pn?.classList.contains('v')) pp(b.getBoundingClientRect()); }, { passive: true }); }); } // —————— Problem 页面劫持 —————— function ep() { if (!location.pathname.startsWith('/problem/CF') && !location.pathname.startsWith('/problem/AT')) return; const rmj = location.pathname.startsWith('/problem/CF') ? '1' : '2'; const pid = location.pathname.split('/problem/')[1]; if (!pid) return; w('.side', sc => { const obs = new MutationObserver(() => { sc.querySelectorAll('a').forEach(l => { if (!l.href.includes('/record/list?pid=')) return; const s = l.querySelector('span'); if (s && s.textContent.trim() !== '点击查看') { s.textContent = '点击查看'; s.className = 'lcolor-Blue-3'; s.style.fontWeight = 'bold'; } if (l.dataset.pt) return; l.onclick = null; l.addEventListener('click', e => { e.preventDefault(); e.stopImmediatePropagation(); const u = new URL('/record/list', 'https://www.luogu.com.cn'); const p = new URLSearchParams(location.search); for (const [k, v] of p) u.searchParams.set(k, v); u.searchParams.set('pid', pid); u.searchParams.set('rmj', rmj); location.href = u.toString(); }, true); l.dataset.pt = '1'; l.href = 'javascript:void(0)'; }); }); obs.observe(sc, { childList: true, subtree: true }); }); } // —————— Record List 选择器 —————— function erl() { if (!location.pathname.startsWith('/record/list')) return; w('section b', b => { if (b.dataset.g) return; b.dataset.g = '1'; const s = document.createElement('select'); s.style.cssText = 'margin-left:8px;padding:0px 8px;border-radius:3px;'; s.innerHTML = ``; const p = new URLSearchParams(location.search); s.value = p.get('rmj') || ''; s.onchange = () => { s.value ? p.set('rmj', s.value) : p.delete('rmj'); location.search = p.toString(); }; b.after(s); }); } // —————— 主逻辑 —————— function mn() { ih(); ep(); erl(); if (location.pathname.startsWith('/training') || location.pathname.startsWith('/problem/list')) { st(); } } r(mn); let lp = location.pathname; setInterval(() => { const cp = location.pathname; if (cp === lp) return; if (lp.startsWith('/training') || lp.startsWith('/problem/list')) sp(); if (cp.startsWith('/training') || cp.startsWith('/problem/list')) st(); lp = cp; }, 100); addEventListener('beforeunload', () => sp()); })();