// ==UserScript== // @name GenGen-RMJ // @icon https://cdn.luogu.com.cn/upload/image_hosting/yy27itbl.png // @namespace https://gengen.qzz.io/ // @version 5.3 // @description GenGen RMJ // @author GenGen Dev // @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://atcoder.jp/contests/*/submissions/me // @match https://codeforces.com/* // @connect * // @inject-into page // @grant GM_addStyle // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant unsafeWindow // @license MIT // ==/UserScript== // 重定向遮罩 (() => { const h = location.href; if (h.includes('challenges.cloudflare.com')) return; let flag = ''; try { flag = GM_getValue('rmj-flag-action', ''); } catch { /* ignore */ } const ref = document.referrer || ''; if (!(ref.includes('RMJ=1') || flag === 'submit')) return; const isStatus = h.includes('codeforces.com/problemset/status') || (h.includes('atcoder.jp/contests/') && h.includes('/submissions/me')); if (!isStatus) return; const m = document.createElement('div'); m.id = 'rmj-redirect-mask'; m.style.cssText = 'position:fixed;inset:0;z-index:2147483647;background:#efefef;'; const attach = () => { if (!document.getElementById('rmj-redirect-mask')) { (document.documentElement || document.body).appendChild(m); } }; if (document.documentElement) attach(); else new MutationObserver((_, o) => { if (document.documentElement) { o.disconnect(); attach(); } }).observe(document, { childList: true }); })(); (() => { 'use strict'; const TAG = '[RMJ]'; const LOG = (...a) => console.log(TAG, ...a); const ERR = (...a) => console.error(TAG, ...a); const HOST = location.hostname; const isLuogu = () => HOST === 'www.luogu.com.cn'; const isCF = () => HOST === 'codeforces.com'; const isAT = () => HOST === 'atcoder.jp'; const isCfChallenge = () => HOST === 'challenges.cloudflare.com'; const docReady = (fn) => document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', fn, { once: true }) : fn(); const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); const waitFor = (selector, cb, timeout = 12000) => { const t0 = Date.now(); const timer = setInterval(() => { const el = document.querySelector(selector); if (el) { clearInterval(timer); cb(el); } else if (Date.now() - t0 > timeout) { clearInterval(timer); } }, 120); return timer; }; const waitForEl = (selector, timeout = 10000) => new Promise((resolve, reject) => { const hit = document.querySelector(selector); if (hit) return resolve(hit); const root = document.body || document.documentElement; const obs = new MutationObserver(() => { const el = document.querySelector(selector); if (el) { obs.disconnect(); clearTimeout(timer); resolve(el); } }); obs.observe(root, { childList: true, subtree: true }); const timer = setTimeout(() => { obs.disconnect(); reject(new Error('超时: ' + selector)); }, timeout); }); const Timers = () => { const set = new Set(); return { iv(fn, ms) { const id = setInterval(fn, ms); set.add(id); return id; }, to(fn, ms) { const id = setTimeout(() => { set.delete(id); fn(); }, ms); set.add(id); return id; }, clear(id) { set.delete(id); clearInterval(id); clearTimeout(id); }, clearAll() { for (const id of set) { clearInterval(id); clearTimeout(id); } set.clear(); } }; }; // 主题存储 const ThemeStore = (() => { const P = 'gen-at-'; const get = (k) => GM_getValue(P + k, ''); const set = (k, v) => GM_setValue(P + k, String(v)); // 从洛谷 .theme-page 提取,找不到容器则不写入 const extractFromLuogu = () => { const el = document.querySelector('.theme-page.theme-frosted') || document.querySelector('.theme-page') || document.querySelector('[class*="theme-page"]'); if (!el) return false; const inlineStyle = (el.getAttribute('style') || '').replace(/"/g, '"'); let computed = null; try { computed = getComputedStyle(el); } catch { computed = null; } const readVar = (name) => { if (inlineStyle) { const re = new RegExp( name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\s*:\\s*([^;]+)' ); const m = inlineStyle.match(re); if (m && m[1].trim()) return m[1].trim(); } if (computed) { const v = computed.getPropertyValue(name); if (v && v.trim()) return v.trim(); } return ''; }; const vars = { body_back: '--theme-body-back', bg_image: '--theme-body-image', bg_color: '--theme-body-color', bg_repeat: '--theme-body-image-repeat', bg_size: '--theme-body-image-size', bg_pos: '--theme-body-image-position', mid_mask: '--theme-body-mid-mask', color_filter: '--theme-body-color-filter', card_bg: '--theme-card-background', card_blur: '--theme-card-backdrop-filter', }; let found = false; for (const [key, cssVar] of Object.entries(vars)) { const v = readVar(cssVar); if (v) { set(key, v); found = true; } } try { const nu = document.querySelector('#app .user-nav .title span'); if (nu && nu.textContent.trim()) set('luogu_user', nu.textContent.trim()); const av = document.querySelector('#app .top-bar .avatar img'); if (av && av.src) set('luogu_avatar', av.src); } catch { /* ignore */ } return found; }; const extractWithRetry = (maxAttempts = 25, interval = 200) => { let n = 0; const tryIt = () => { if (extractFromLuogu()) return; if (++n < maxAttempts) setTimeout(tryIt, interval); }; tryIt(); }; // 应用到任意遮罩容器 const applyTo = (el) => { if (!el) return; const b64 = get('bg_b64'); const raw = get('bg_image'); let img = ''; if (b64) { img = `url(${b64})`; } else if (raw) { const m = raw.match(/url\(\s*(['"]?)(.+?)\1\s*\)/i); let u = m ? m[2] : raw; if (u && !/^https?:\/\//i.test(u) && !/^data:/i.test(u)) { try { u = new URL(u, 'https://www.luogu.com.cn/').href; } catch { /* keep */ } } if (u && u !== 'none') img = /^url\(/i.test(raw) ? `url(${u})` : `url(${u})`; } const M = { '--rmj-back': get('body_back') || '#f5f5f5', '--rmj-img': img, '--rmj-color': get('bg_color'), '--rmj-repeat': get('bg_repeat') || 'no-repeat', '--rmj-size': get('bg_size') || 'cover', '--rmj-pos': get('bg_pos') || 'center', '--rmj-mask': get('mid_mask') || 'none', '--rmj-filter': get('color_filter') || 'none', '--rmj-card': get('card_bg') || 'rgba(255,255,255,.78)', '--rmj-blur': get('card_blur') || 'blur(14px)', }; for (const [k, v] of Object.entries(M)) { if (v) el.style.setProperty(k, v); } }; return { get, set, applyTo, extractFromLuogu, extractWithRetry }; })(); // 共享遮罩样式 GM_addStyle(` .rmj-ovl-base{ position:fixed!important;inset:0!important;z-index:2147483647!important; display:flex!important;align-items:center!important;justify-content:center!important; overflow:hidden!important;pointer-events:all!important;cursor:default!important; background:var(--rmj-back,#f5f5f5)!important; color:#333!important;box-sizing:border-box!important;-webkit-font-smoothing:antialiased!important; } .rmj-ovl-base *{box-sizing:border-box!important;margin:0!important;padding:0!important;font-family:inherit!important;line-height:inherit!important} .rmj-ovl-base .rmj-ly{position:absolute!important;inset:0!important;pointer-events:none!important; -webkit-mask:var(--rmj-mask,none) no-repeat/100% 100%!important;mask:var(--rmj-mask,none) no-repeat/100% 100%!important} .rmj-ovl-base .rmj-ly-c{background:var(--rmj-color,transparent)!important;filter:var(--rmj-filter,none)!important} .rmj-ovl-base .rmj-ly-i{background:var(--rmj-img,none) var(--rmj-pos,center)/var(--rmj-size,cover) var(--rmj-repeat,no-repeat)!important} .rmj-ovl-card{ position:relative!important;z-index:1!important;width:420px!important;max-width:90vw!important; padding:36px 32px 40px!important;border-radius:4px!important;border:none!important; background:var(--rmj-card,rgba(255,255,255,.78))!important; backdrop-filter:var(--rmj-blur,blur(14px))!important;-webkit-backdrop-filter:var(--rmj-blur,blur(14px))!important; box-shadow:0 8px 32px rgba(0,0,0,.18),0 1.5px 6px rgba(0,0,0,.08)!important; display:flex!important;flex-direction:column!important;align-items:center!important;gap:20px!important; } .rmj-ovl-card h2{font-size:20px!important;font-weight:600!important;color:#333!important;letter-spacing:.5px!important;user-select:none!important;text-align:center!important} .rmj-ovl-card p{font-size:13px!important;color:#888!important;text-align:center!important;user-select:none!important} .rmj-ovl-spin{width:26px;height:26px;border:3px solid rgba(0,0,0,.1);border-top-color:#005c97;border-radius:50%;animation:rmj-ovl-spin .7s linear infinite} @keyframes rmj-ovl-spin{to{transform:rotate(360deg)}} `); // 路由 const Router = (() => { const handlers = new Set(); let last = location.href; const notify = () => { const cur = location.href; if (cur === last) return; last = cur; for (const fn of handlers) { try { fn(cur); } catch (e) { ERR('路由回调异常', e); } } }; const wrap = (name) => { const orig = history[name]; if (!orig || orig.__rmjWrapped) return; const wrapped = function (...args) { const ret = orig.apply(this, args); setTimeout(notify, 0); return ret; }; wrapped.__rmjWrapped = true; history[name] = wrapped; }; wrap('pushState'); wrap('replaceState'); addEventListener('popstate', notify, true); addEventListener('hashchange', notify, true); return { on(fn) { handlers.add(fn); return () => handlers.delete(fn); }, notify, }; })(); const CACHE_CF = 'cf-submissions-cache'; const CACHE_AT = 'at-submissions-cache'; const LAST_UPDATE_CF = 'cf-last-update'; const LAST_UPDATE_AT = 'at-last-update'; const SYNC_INTERVAL_MS = 24 * 60 * 60 * 1000; const FINAL_AT = new Set(['AC', 'WA', 'CE', 'RE', 'TLE', 'MLE', 'OLE', 'IE']); // CF 挑战页补丁 const CfChallengePatch = { applied: false, apply() { if (this.applied || !isCfChallenge()) return; this.applied = true; const XHR = (unsafeWindow || window).XMLHttpRequest; if (!XHR) return; const origOpen = XHR.prototype.open; const origSend = XHR.prototype.send; XHR.prototype.open = function (method, url) { this._rmjUrl = url || ''; return origOpen.apply(this, arguments); }; XHR.prototype.send = function (body) { if (this._rmjUrl) this.timeout = undefined; return origSend.apply(this, arguments); }; } }; // 洛谷主界面 const MainUI = (() => { const VERSION = '5.3'; const POLL_MS = 1000; const SVG = { ok: ``, err: ``, na: `` }; const C = { ac: '#52c41a', wa: '#e74c3c', na: '#404040', p: '#0ea5e9', bd: '#e2e8f0', t: '#1e293b', m: '#64748b', bg: '#fff', alt: '#f8fafc' }; 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-weight:600;font-size:13px;cursor:pointer;transition:border-color .18s ease,color .18s ease,box-shadow .18s ease;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 10px rgba(14,165,233,.14)} .rmj-trigger:active{box-shadow:0 1px 3px rgba(14,165,233,.18) inset} .rmj-trigger img{width:20px;height:20px;border-radius:4px} .rmj-overlay{pointer-events:none;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:opacity .22s ease,visibility .22s ease} .rmj-overlay.show{opacity:1;visibility:visible;pointer-events:auto} .rmj-modal{width:480px;max-height:85vh;background:rgba(255,255,255,0.82);backdrop-filter:blur(10px);border-radius:12px;box-shadow:0 25px 60px rgba(0,0,0,.14);display:flex;flex-direction:column;overflow:hidden;transform:scale(.97);transition:transform .25s cubic-bezier(.25,.8,.32,1),height .25s ease} .rmj-overlay.show .rmj-modal{transform:scale(1)} .rmj-overlay:not(.show) .rmj-modal *{pointer-events:none!important;cursor:default!important} .rmj-overlay:not(.show) .rmj-modal a{color:inherit!important;text-decoration:none!important} .rmj-pages{flex:1;overflow:hidden;position:relative;min-height:0;height:auto;transition:height .28s 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-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-weight:700;font-size:20px;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:background .15s ease,color .15s ease} .rmj-close:hover{background:${C.alt};color:${C.t}} .rmj-pages{display:grid;width:100%;overflow:hidden;transition:height .28s 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 .25s ease,visibility .25s} .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:border-color .18s ease,box-shadow .18s ease,background .18s ease} .rmj-card:hover{border-color:rgba(14,165,233,.35);box-shadow:0 2px 10px rgba(14,165,233,.08);background:#fff} .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:rgba(82,196,26,.09);color:${C.ac}} .rmj-badge.off{background:rgba(231,76,60,.09);color:${C.wa};cursor:pointer;text-decoration:none;transition:background .15s ease,color .15s ease} .rmj-badge.off:hover{background:rgba(231,76,60,.16)} .rmj-badge.load{background:#f1f5f9;color:${C.m}} .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:border-color .18s ease,color .18s ease,box-shadow .18s ease,background .18s ease} .rmj-link:hover{border-color:${C.p};color:${C.p};background:#fff;box-shadow:0 2px 10px rgba(14,165,233,.10)} .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:border-color .15s ease,color .15s ease,background .15s ease} .rmj-btn:hover:not(:disabled){border-color:${C.p};color:${C.p};background:#f8fafc} .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:background .2s ease,width .2s ease} .rmj-dot.active{background:${C.p};width:18px;border-radius:3px} .rmj-prob{display:inline-flex;align-items:center;transition:color .3s ease} .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:transform .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:opacity .2s ease,transform .2s ease;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:color .18s ease,transform .25s ease} .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:background .2s ease;border-radius:22px} .rmj-slider:before{position:absolute;content:"";height:16px;width:16px;left:3px;bottom:3px;background:#fff;transition:transform .2s ease;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} `); const cfMap = new Map(); const atMap = new Map(); let cfRawCache = ''; let atRawCache = ''; let atCapturing = false; let listTimer = null; let problemTimer = null; let problemObserver = null; let problemGen = 0; let currentProblemPath = ''; let modalEl = null; let modalPage = 0; let cfPollTimer = null; let atLoginPollTimer = 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 isSyncDisabled = () => { if (isSpecialAccount()) return true; let fromParams = false; try { const params = new URLSearchParams(location.search); fromParams = params.get('rmj-disable-sync') === 'true'; } catch (e) { /* ignore */ } if (fromParams) return true; if (GM_getValue('rmj-disable-sync', null) === null) { GM_setValue('rmj-disable-sync', false); } return GM_getValue('rmj-disable-sync', false) === true; }; const captureCfHandle = () => { if (!isCF()) return; const attempt = () => { const el = document.querySelector('#header > div.lang-chooser > div:nth-child(2) > a:nth-child(1)'); if (!el) return setTimeout(attempt, 1000); const text = el.textContent.trim(); if (text && !['Enter', '登录'].includes(text)) { const stored = GM_getValue('cf-handle', ''); if (stored !== text) GM_setValue('cf-handle', text); } }; attempt(); }; // 拉取 AT 用户名;未登录返回 null const fetchAtHandle = () => new Promise((resolve) => { GM_xmlhttpRequest({ method: 'GET', url: 'https://atcoder.jp/', timeout: 8000, onload: (r) => { if (r.status !== 200) return resolve(null); 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) return resolve(null); const t = el.textContent.trim(); if (!t || ['Sign Up', 'Login', '登录'].includes(t)) return resolve(null); resolve(t); } catch { resolve(null); } }, onerror: () => resolve(null), ontimeout: () => resolve(null), }); }); const captureAtHandle = async () => { atCapturing = true; updateModal(); const handle = await fetchAtHandle(); if (handle) { const stored = GM_getValue('at-handle', ''); if (stored !== handle) GM_setValue('at-handle', handle); atCapturing = false; return updateModal(); } // 未登录:打开登录页并 5s 轮询 try { window.open('https://atcoder.jp/login?lang=en', '_blank'); } catch { /* ignore */ } if (atLoginPollTimer) clearInterval(atLoginPollTimer); let polls = 0; atLoginPollTimer = setInterval(async () => { polls++; if (polls > 60) { clearInterval(atLoginPollTimer); atLoginPollTimer = null; atCapturing = false; updateModal(); return; } const h = await fetchAtHandle(); if (h) { clearInterval(atLoginPollTimer); atLoginPollTimer = null; const stored = GM_getValue('at-handle', ''); if (stored !== h) GM_setValue('at-handle', h); atCapturing = false; updateModal(); } }, 5000); }; const readCaches = () => { try { const cfData = GM_getValue(CACHE_CF, '[]'); const atData = GM_getValue(CACHE_AT, '[]'); if (cfData !== cfRawCache) { cfRawCache = cfData; cfMap.clear(); JSON.parse(cfData).forEach((s) => { const m = s?.taskDisplay?.match(/CF[A-Z0-9]+/i); if (!m) return; const key = m[0].toUpperCase(); const status = s.status || null; if (!cfMap.has(key) || (cfMap.get(key) !== 'OK' && status === 'OK')) { cfMap.set(key, status); } }); } if (atData !== atRawCache) { atRawCache = atData; atMap.clear(); JSON.parse(atData).forEach((s) => { if (!s?.taskKey) return; const key = s.taskKey.toLowerCase(); const status = s.status || null; if (!atMap.has(key) || (atMap.get(key) !== 'AC' && status === 'AC')) { atMap.set(key, status); } }); } } catch (e) { ERR(e); } }; const queryStatus = (pid) => { if (/^CF[A-Z0-9]+$/i.test(pid)) { const v = cfMap.get(pid.toUpperCase()); return v === 'OK' ? 'AC' : v ? 'WA' : 'NONE'; } if (/^AT_/i.test(pid)) { const k = pid.replace(/^AT_/i, '').toLowerCase(); const v = atMap.get(k); return v === 'AC' ? 'AC' : v ? 'WA' : 'NONE'; } return null; }; const updateListIcons = () => { readCaches(); 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 = queryStatus(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 startListPoll = () => { if (listTimer) return; updateListIcons(); listTimer = setInterval(updateListIcons, POLL_MS); }; const stopListPoll = () => { if (!listTimer) return; clearInterval(listTimer); listTimer = null; }; const updateProblemBadge = () => { readCaches(); const pid = location.pathname.split('/problem/')[1]; if (!pid) return; const st = queryStatus(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') { 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 startProblemPoll = () => { if (problemTimer) return; updateProblemBadge(); problemTimer = setInterval(updateProblemBadge, POLL_MS); }; const stopProblemPoll = () => { if (!problemTimer) return; clearInterval(problemTimer); problemTimer = null; }; const teardownProblemObserver = () => { problemGen++; if (problemObserver) { problemObserver.disconnect(); problemObserver = null; } currentProblemPath = ''; }; const startCfPoll = () => { if (cfPollTimer) clearInterval(cfPollTimer); const initialHandle = GM_getValue('cf-handle', ''); let checks = 0; cfPollTimer = setInterval(() => { checks++; const current = GM_getValue('cf-handle', ''); if (current !== initialHandle || checks > 60) { clearInterval(cfPollTimer); cfPollTimer = null; updateModal(); } }, 1000); updateModal(); }; const buildModal = () => { if (modalEl) modalEl.remove(); modalEl = document.createElement('div'); modalEl.className = 'rmj-overlay'; modalEl.innerHTML = `

GenGen RMJ

👋 欢迎使用 GenGen RMJ 5 正式版 v${VERSION}

在洛谷上无缝刷 Codeforces 和 AtCoder 题目,同步提交记录。

  • 请点击下一页来绑定你的账户!
  • 自动同步 CF/AT 账号提交记录
  • 题目列表和题单直观显示通过状态
  • Cloudflare 验证加速绕过
  • 记录详细查看功能
  • 最新更新:完全适配新前端
账号绑定
Codeforces
AtCoder
设置
自动同步
快捷链接
`; document.body.appendChild(modalEl); modalEl.querySelector('.rmj-close').onclick = () => { GM_setValue('rmj-version-shown', VERSION); hideModal(); modalEl.addEventListener('click', (e) => { if (!modalEl.classList.contains('show')) { e.stopImmediatePropagation(); e.preventDefault(); } }, true); }; modalEl.querySelector('.rmj-prev').onclick = () => switchModalPage(0); modalEl.querySelector('.rmj-next').onclick = () => switchModalPage(1); updateModal(); }; const updateModal = () => { if (!modalEl) return; const cfH = GM_getValue('cf-handle', ''); const atH = GM_getValue('at-handle', ''); const cfEl = modalEl.querySelector('#rmj-cf'); const atEl = modalEl.querySelector('#rmj-at'); const refreshIcon = ``; if (cfPollTimer) { cfEl.innerHTML = `同步中...`; } else if (cfH) { cfEl.innerHTML = `${cfH}${refreshIcon}`; } else { cfEl.innerHTML = `去绑定 →`; } if (atCapturing) { atEl.innerHTML = `等待登录...`; } else if (atH) { atEl.innerHTML = `${atH}${refreshIcon}`; } else { atEl.innerHTML = `去绑定 →`; } const cfBind = modalEl.querySelector('#rmj-cf-bind'); if (cfBind) cfBind.onclick = () => { startCfPoll(); }; const cfRefresh = modalEl.querySelector('#rmj-cf .rmj-refresh-wrap'); if (cfRefresh) { cfRefresh.onclick = () => { window.open('https://codeforces.com/enter', '_blank'); startCfPoll(); }; } const atBind = modalEl.querySelector('#rmj-at-bind'); if (atBind) { atBind.onclick = (e) => { e.preventDefault(); captureAtHandle(); }; } const atRefresh = modalEl.querySelector('#rmj-at .rmj-refresh-wrap'); if (atRefresh) atRefresh.onclick = () => captureAtHandle(); const syncToggle = modalEl.querySelector('#rmj-disable-sync'); if (syncToggle) { const isDisabled = isSyncDisabled(); syncToggle.checked = !isDisabled; if (isSpecialAccount()) { syncToggle.checked = false; syncToggle.disabled = true; syncToggle.title = '特殊账号无法修改同步设置'; } syncToggle.onchange = async () => { if (isSpecialAccount()) { syncToggle.checked = false; return; } GM_setValue(CACHE_AT, '[]'); GM_setValue(CACHE_CF, '[]'); GM_setValue(LAST_UPDATE_AT, '0'); GM_setValue('cf-0last-update', '0'); updateListIcons(); updateProblemBadge(); }; } }; const switchModalPage = (idx) => { if (!modalEl || idx === modalPage) return; const pagesContainer = modalEl.querySelector('.rmj-pages'); const allPages = modalEl.querySelectorAll('.rmj-page'); const targetPage = allPages[idx]; const dots = modalEl.querySelectorAll('.rmj-dot'); const prevBtn = modalEl.querySelector('.rmj-prev'); const nextBtn = modalEl.querySelector('.rmj-next'); allPages.forEach((p, i) => p.classList.toggle('active', i === idx)); if (targetPage && pagesContainer) { pagesContainer.style.height = targetPage.scrollHeight + 'px'; } dots.forEach((d, i) => d.classList.toggle('active', i === idx)); prevBtn.disabled = idx === 0; nextBtn.disabled = idx === dots.length - 1; modalPage = idx; if (idx === 1 && !GM_getValue('at-handle', '') && !atCapturing) { fetchAtHandle().then((h) => { if (h) { const stored = GM_getValue('at-handle', ''); if (stored !== h) GM_setValue('at-handle', h); updateModal(); } }); } }; const showModal = (page = 0) => { if (!modalEl) buildModal(); modalPage = page; const pagesContainer = modalEl.querySelector('.rmj-pages'); const allPages = modalEl.querySelectorAll('.rmj-page'); const targetPage = allPages[page]; const dots = modalEl.querySelectorAll('.rmj-dot'); const prevBtn = modalEl.querySelector('.rmj-prev'); const nextBtn = modalEl.querySelector('.rmj-next'); allPages.forEach((p, i) => p.classList.toggle('active', i === page)); requestAnimationFrame(() => { if (targetPage && pagesContainer) { pagesContainer.style.height = targetPage.scrollHeight + 'px'; } }); dots.forEach((d, i) => d.classList.toggle('active', i === page)); prevBtn.disabled = page === 0; nextBtn.disabled = page === dots.length - 1; modalEl.style.pointerEvents = ''; modalEl.classList.add('show'); }; const hideModal = () => { if (!modalEl) return; modalEl.classList.remove('show'); modalEl.style.pointerEvents = 'none'; }; const checkVersion = () => { if (GM_getValue('rmj-version-shown', '') !== VERSION) { setTimeout(() => showModal(0), 500); } }; const injectHomeButton = () => { if (location.pathname !== '/' || document.querySelector('.rmj-trigger')) return; waitFor('.right', (nav) => { if (nav.dataset.rmj) return; nav.dataset.rmj = '1'; const btn = document.createElement('button'); btn.className = 'rmj-trigger'; btn.innerHTML = `GenGen RMJ`; btn.onclick = () => showModal(1); nav.parentElement.insertBefore(btn, nav); }); }; const initProblemPage = () => { const p = location.pathname; if (!p.startsWith('/problem/CF') && !p.startsWith('/problem/AT')) return; if (currentProblemPath === p && problemObserver) return; currentProblemPath = p; const myPath = p; const myGen = ++problemGen; const rmj = p.startsWith('/problem/CF') ? '1' : '2'; const pid = p.split('/problem/')[1]; if (!pid) return; if (problemObserver) { problemObserver.disconnect(); problemObserver = null; } waitFor('.side', (side) => { if (myGen !== problemGen || currentProblemPath !== myPath) return; problemObserver = 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); }); updateProblemBadge(); }); problemObserver.observe(side, { childList: true, subtree: true }); }); updateProblemBadge(); startProblemPoll(); }; const injectRecordListSelector = () => { if (!location.pathname.startsWith('/record/list')) return; const myGen = problemGen; waitFor( '#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 (myGen !== problemGen) return; const cur = new URLSearchParams(location.search).get('rmj') || ''; let w = document.querySelector('.rmj-sel'); if (w) { const btn = w.querySelector('.rmj-sel-btn'); const menu = w.querySelector('.rmj-sel-menu'); if (btn && menu) { const act = menu.querySelector(`li[data-value="${cur}"]`); if (act && btn.textContent !== act.textContent) { btn.textContent = act.textContent; } } return; } 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); if (val) p.set('rmj', val); else p.delete('rmj'); location.search = p.toString(); }); }); document.addEventListener('click', () => { w.classList.remove('open'); menu.classList.remove('open'); }); const act = menu.querySelector(`li[data-value="${cur}"]`); if (act) btn.textContent = act.textContent; } ); }; const handleRoute = () => { if (!isLuogu()) return; const path = location.pathname; ThemeStore.extractWithRetry(15, 250); if (path === '/') injectHomeButton(); if (path.startsWith('/problem/CF') || path.startsWith('/problem/AT')) { initProblemPage(); } else { stopProblemPoll(); teardownProblemObserver(); } if (path.startsWith('/training') || path.startsWith('/problem/list')) { startListPoll(); } else { stopListPoll(); } if (path.startsWith('/record/list')) { injectRecordListSelector(); } }; const init = () => { if (!isLuogu()) return; checkVersion(); handleRoute(); addEventListener('beforeunload', () => { stopListPoll(); stopProblemPoll(); }); }; return { init, handleRoute, hideModal, captureCfHandle }; })(); // 洛谷记录详情页(DET) const RecordDetail = (() => { const _gmGet = GM_getValue; const _gmReq = GM_xmlhttpRequest; const VALID = [ /^https:\/\/www\.luogu\.com\.cn\/record\/cf\/[^/]+\/[^/?#]+/, /^https:\/\/www\.luogu\.com\.cn\/record\/at\/[^/]+\/[^/?#]+/, ]; const CF2LG = { OK: 12, WRONG_ANSWER: 6, TIME_LIMIT_EXCEEDED: 5, MEMORY_LIMIT_EXCEEDED: 4, OUTPUT_LIMIT_EXCEEDED: 3, RUNTIME_ERROR: 7, COMPILATION_ERROR: 2, TESTING: 1, WAITING: 0 }; const FINAL_CF = new Set(['OK', 'WRONG_ANSWER', 'COMPILATION_ERROR', 'RUNTIME_ERROR', 'TIME_LIMIT_EXCEEDED', 'MEMORY_LIMIT_EXCEEDED', 'OUTPUT_LIMIT_EXCEEDED']); GM_addStyle(` #rmj-det-overlay.rmj-ovl-base{z-index:2147483647!important} #rmj-det-card{width:420px!important;max-width:90vw!important} `); const gmJson = (url) => new Promise((ok, no) => { _gmReq({ method: 'GET', url, timeout: 15000, anonymous: true, onload: (r) => { try { ok(JSON.parse(r.responseText)); } catch (e) { no(e); } }, onerror: () => no(new Error('网络错误')), ontimeout: () => no(new Error('请求超时')) }); }); const gmText = (url) => new Promise((ok, no) => { _gmReq({ method: 'GET', url, timeout: 15000, anonymous: true, onload: (r) => r.status === 200 ? ok(r.responseText) : no(new Error('状态码 ' + r.status)), onerror: () => no(new Error('网络错误')), ontimeout: () => no(new Error('请求超时')) }); }); let lastKey = ''; let generation = 0; const timers = Timers(); const buildLoader = () => { const el = document.createElement('div'); el.id = 'rmj-det-overlay'; el.className = 'rmj-ovl-base'; el.innerHTML = `

正在加载提交记录

正在从 CF/AT 获取数据

`; ThemeStore.applyTo(el); return el; }; // 未绑定提示 const showBindError = (bindName) => { const el = document.createElement('div'); el.id = 'rmj-det-overlay'; el.className = 'rmj-ovl-base'; el.innerHTML = `

账号未绑定

请先在 GenGen RMJ 面板中绑定 ${bindName} 账号

前往洛谷主页绑定
`; ThemeStore.applyTo(el); (document.documentElement || document.body).appendChild(el); }; const destroy = () => { generation++; timers.clearAll(); const mask = document.getElementById('rmj-det-overlay'); if (mask) mask.remove(); lastKey = ''; }; const init = () => { if (!VALID.some((r) => r.test(location.href))) { if (lastKey) destroy(); return; } const m = location.pathname.match(/\/record\/(cf|at)\/([^/]+)\/([^/?#]+)/); if (!m) return; const [, plat, cid, sid] = m; const key = `${plat}/${cid}/${sid}`; if (lastKey === key) return; destroy(); lastKey = key; const myGen = generation; const isCFPage = plat === 'cf'; const isATPage = plat === 'at'; // 未绑定则拒绝查看 const bindKey = isCFPage ? 'cf-handle' : 'at-handle'; const bindName = isCFPage ? 'Codeforces' : 'AtCoder'; if (!_gmGet(bindKey, '')) { showBindError(bindName); return; } const handle = _gmGet('cf-handle', '') || _gmGet('rmj-cf-handle', '') || ''; const code = _gmGet(`{${cid},${sid}}`, ''); const titleTimer = timers.iv(() => { document.title = 'GenGen RMJ 加载中'; }, 100); const mask = buildLoader(); (document.documentElement || document.body || document).appendChild(mask); let cachedTpl = null; const fetchTemplate = async () => { if (cachedTpl) return cachedTpl; return new Promise((ok, no) => { _gmReq({ method: 'GET', url: 'https://www.luogu.com.cn/record/290510067', timeout: 15000, onload: (r) => { if (r.status === 200) { cachedTpl = r.responseText; ok(cachedTpl); } else no(new Error('模板获取失败')); }, onerror: () => no(new Error('模板网络错误')), ontimeout: () => no(new Error('模板请求超时')) }); }); }; async function findCF() { let url = `https://codeforces.com/api/contest.status?contestId=${encodeURIComponent(cid)}&from=1&count=100`; if (handle) url += `&handle=${encodeURIComponent(handle)}`; const d = await gmJson(url); if (d.status !== 'OK') throw new Error('CF ' + d.status); return (d.result || []).find((x) => String(x.id) === sid) || null; } function parseATStatus(text) { if (!text) return 'TESTING'; text = text.trim(); if (text === 'AC') return 'OK'; if (text === 'WA') return 'WRONG_ANSWER'; if (text === 'TLE' || text.includes('TLE')) return 'TIME_LIMIT_EXCEEDED'; if (text === 'MLE') return 'MEMORY_LIMIT_EXCEEDED'; if (text === 'OLE') return 'OUTPUT_LIMIT_EXCEEDED'; if (text === 'RE') return 'RUNTIME_ERROR'; if (text === 'CE') return 'COMPILATION_ERROR'; if (text === 'WJ') return 'WAITING'; if (text.includes('WA')) return 'WRONG_ANSWER'; return 'TESTING'; } async function findAT() { const url = `https://atcoder.jp/contests/${cid}/submissions/${sid}`; const html = await gmText(url); const doc = new DOMParser().parseFromString(html, 'text/html'); const metaTable = doc.querySelector('table.table-bordered'); const metaRows = metaTable ? metaTable.querySelectorAll('tr') : []; const meta = {}; metaRows.forEach((row) => { const th = row.querySelector('th'); const td = row.querySelector('td'); if (th && td) meta[th.textContent.trim()] = td.textContent.trim(); }); const codeEl = doc.querySelector('#submission-code'); const sourceCode = codeEl ? codeEl.textContent : ''; const tables = doc.querySelectorAll('table.table-bordered'); const cases = []; let passedCount = 0; let caseTable = null; for (const t of tables) { const header = t.querySelector('thead th, tr th'); if (header && (header.textContent.includes('Case Name') || header.textContent.includes('Status'))) { caseTable = t; break; } } if (caseTable) { caseTable.querySelectorAll('tbody tr').forEach((row, index) => { const tds = row.querySelectorAll('td'); if (tds.length >= 4) { const statusText = tds[1].textContent.trim(); const timeMatch = tds[2].textContent.trim().match(/(\d+)/); const memMatch = tds[3].textContent.trim().match(/(\d+)/); const verdict = parseATStatus(statusText); if (verdict === 'OK') passedCount++; cases.push({ id: `${index + 1}`, status: verdict, time: timeMatch ? parseInt(timeMatch[1]) : null, memKB: memMatch ? parseInt(memMatch[1]) : null }); } }); } let overallVerdict = parseATStatus(meta['Status'] || ''); if (overallVerdict === 'OK' && passedCount < cases.length && cases.length > 0) { overallVerdict = 'TESTING'; } const timeMatch = (meta['Exec Time'] || '').match(/(\d+)/); const memMatch = (meta['Memory'] || '').match(/(\d+)/); const taskText = meta['Task'] || ''; let taskIndex = 'a'; const taskLink = metaTable ? metaTable.querySelector('a[href*="/tasks/"]') : null; if (taskLink) { const linkMatch = (taskLink.getAttribute('href') || '').match(/\/tasks\/[^_]+_(.+)$/i); if (linkMatch) taskIndex = linkMatch[1].toLowerCase(); } else { const textMatch = taskText.match(/^([A-Za-z0-9]+)/); if (textMatch) taskIndex = textMatch[1].toLowerCase(); } let submitTime = 0; const timeStr = meta['Submission Time'] || ''; if (timeStr) { const tm = timeStr.match(/^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2})([+-]\d{2})(\d{2})$/); submitTime = tm ? Math.floor(new Date(`${tm[1]}T${tm[2]}${tm[3]}:${tm[4]}`).getTime() / 1000) : (Math.floor(new Date(timeStr).getTime() / 1000) || 0); } return { isAT: true, verdict: overallVerdict, passedTestCount: passedCount, timeConsumedMillis: timeMatch ? parseInt(timeMatch[1]) : 0, memoryConsumedBytes: memMatch ? parseInt(memMatch[1]) * 1024 : 0, problem: { name: taskText, index: taskIndex }, sourceCode, cases, creationTimeSeconds: submitTime }; } function buildCases(sub, time, memKB) { if (sub.isAT) { return sub.cases.map((c, index) => ({ id: index, status: CF2LG[c.status] ?? 0, time: c.time ?? 0, memory: c.memKB ?? 0, score: 1, signal: 0, exitCode: 0, description: '', subtaskID: 0 })); } const lgCode = CF2LG[sub.verdict] ?? 0; const isAC = sub.verdict === 'OK'; const isCE = sub.verdict === 'COMPILATION_ERROR'; const passed = sub.passedTestCount ?? 0; if (isCE) return []; const idNum = isAC ? (passed > 0 ? passed - 1 : 0) : passed; let status = isAC ? 12 : lgCode; if (sub.verdict === 'TESTING') status = 1; return [{ id: idNum, status, time: time ?? 0, memory: memKB ?? 0, score: 100, signal: 0, exitCode: 0, description: '', subtaskID: 0 }]; } function buildHtml(tpl, sub) { const FE_RE = /([\s\S]*?)<\/script>/i; const fm = tpl.match(FE_RE); if (!fm) return tpl; let data; try { data = JSON.parse(fm[1]); } catch (e) { return tpl; } const rec = data?.data?.record; if (!rec) return tpl; const cu = data?.data?.user || data?.user; if (rec.user && cu) { Object.assign(rec.user, { uid: cu.uid, name: cu.name, avatar: cu.avatar, color: cu.color || '', slogan: cu.slogan || '' }); } if (isATPage && sub && sub.sourceCode) { rec.sourceCode = sub.sourceCode; rec.sourceCodeLength = sub.sourceCode.length; } else if (code) { rec.sourceCode = code; rec.sourceCodeLength = code.length; } else { rec.sourceCode = '// 未找到该提交的源代码\n// 请前往原站查看'; rec.sourceCodeLength = 0; } if ((isCFPage || isATPage) && sub) { const lgCode = CF2LG[sub.verdict] ?? 0; const isFinal = FINAL_CF.has(sub.verdict); const time = isFinal ? (sub.timeConsumedMillis ?? 0) : 0; const memKB = isFinal ? Math.round((sub.memoryConsumedBytes ?? 0) / 1024) : 0; if (rec.problem && sub.problem) { if (isCFPage) { rec.problem.pid = `CF${cid}${sub.problem.index}`; rec.problem.type = 'CF'; } else if (isATPage) { rec.problem.pid = `AT_${cid}_${sub.problem.index}`; rec.problem.type = 'AT'; } rec.problem.name = sub.problem.name; delete rec.problem.title; } rec.status = lgCode; rec.time = time; rec.memory = memKB; rec.id = Number(sid) || rec.id; if (sub.creationTimeSeconds) rec.submitTime = sub.creationTimeSeconds; if (rec.detail?.compileResult) { rec.detail.compileResult.success = sub.verdict !== 'COMPILATION_ERROR'; } const cases = buildCases(sub, time, memKB); if (rec.detail?.judgeResult) { rec.detail.judgeResult.subtasks = [{ id: 0, score: null, status: lgCode, testCases: cases, judger: '', time, memory: memKB }]; rec.detail.judgeResult.finishedCaseCount = cases.length; rec.detail.judgeResult.status = lgCode; rec.detail.judgeResult.time = time; rec.detail.judgeResult.memory = memKB; rec.detail.judgeResult.score = null; } } const newJsonStr = JSON.stringify(data).replace(/${newJsonStr}`); } async function renderPage(sub) { if (myGen !== generation) return; const tpl = await fetchTemplate().catch(() => null); if (!tpl || myGen !== generation) return; const doc = new DOMParser().parseFromString(buildHtml(tpl, sub), 'text/html'); document.replaceChild(doc.documentElement, document.documentElement); document.querySelectorAll('script').forEach((oldScript) => { const newScript = document.createElement('script'); Array.from(oldScript.attributes).forEach((attr) => newScript.setAttribute(attr.name, attr.value)); newScript.textContent = oldScript.textContent; oldScript.parentNode.replaceChild(newScript, oldScript); }); } (async () => { const updateCache = (sub) => { if (!sub || myGen !== generation) return; if (isCFPage) { let cache = []; try { cache = JSON.parse(GM_getValue(CACHE_CF, '[]')); if (!Array.isArray(cache)) cache = []; } catch { cache = []; } const item = { id: String(sid), taskDisplay: `CF${cid}${sub.problem.index} - ${sub.problem.name}`, status: sub.verdict || 'Waiting', timestamp: (sub.creationTimeSeconds || 0) * 1000, submitUrl: `https://www.luogu.com.cn/record/cf/${cid}/${sid}`, luoguUrl: `https://www.luogu.com.cn/problem/CF${cid}${sub.problem.index}`, passedTestCount: sub.passedTestCount }; const idx = cache.findIndex((c) => c.id === String(sid)); if (idx !== -1) cache[idx] = { ...cache[idx], ...item }; else cache.unshift(item); cache.sort((a, b) => Number(b.id) - Number(a.id)); GM_setValue(CACHE_CF, JSON.stringify(cache)); GM_setValue(LAST_UPDATE_CF, Date.now()); } else if (isATPage) { let cache = []; try { cache = JSON.parse(GM_getValue(CACHE_AT, '[]')); if (!Array.isArray(cache)) cache = []; } catch { cache = []; } const AT_VERDICT_MAP = { OK: 'AC', WRONG_ANSWER: 'WA', TIME_LIMIT_EXCEEDED: 'TLE', MEMORY_LIMIT_EXCEEDED: 'MLE', OUTPUT_LIMIT_EXCEEDED: 'OLE', RUNTIME_ERROR: 'RE', COMPILATION_ERROR: 'CE', WAITING: 'WJ', TESTING: 'WJ' }; const atStatus = AT_VERDICT_MAP[sub.verdict] || 'WJ'; const taskKey = `${cid}_${sub.problem.index}`; const item = { id: String(sid), taskKey, taskDisplay: `${taskKey} - ${sub.problem.index.toUpperCase()} - ${sub.problem.name}`, status: atStatus, submitUrl: `https://atcoder.jp/contests/${cid}/submissions/${sid}`, luoguUrl: `https://www.luogu.com.cn/problem/AT_${taskKey}`, timestamp: (sub.creationTimeSeconds || 0) * 1000, isFinal: FINAL_AT.has(atStatus) }; const idx = cache.findIndex((c) => c.id === String(sid)); if (idx !== -1) cache[idx] = { ...cache[idx], ...item }; else cache.unshift(item); cache.sort((a, b) => Number(b.id) - Number(a.id)); GM_setValue(CACHE_AT, JSON.stringify(cache)); GM_setValue(LAST_UPDATE_AT, Date.now()); } }; const cfDomainPath = Number(cid) > 100000 ? 'gym' : 'contest'; const origUrl = isCFPage ? `https://codeforces.com/${cfDomainPath}/${cid}/submission/${sid}` : `https://atcoder.jp/contests/${cid}/submissions/${sid}`; let sub = null; try { sub = isCFPage ? await findCF() : (isATPage ? await findAT() : null); } catch (e) { if (myGen !== generation) return; if (isATPage) { timers.clear(titleTimer); location.replace(origUrl); return; } } if (myGen !== generation) return; if (isCFPage && sub === null) { timers.clear(titleTimer); location.replace(origUrl); return; } if (sub) updateCache(sub); timers.clear(titleTimer); // 已拿到结果直接渲染一次 await renderPage(sub); if (myGen !== generation) return; let currentSub = sub; let atWaitPollingId = null; timers.iv(() => { if (myGen !== generation) return; const m2 = document.getElementById('rmj-det-overlay'); if (m2 && (document.querySelector('#app') || document.querySelector('.lfe-body'))) m2.remove(); const hasCode = (isATPage && currentSub && currentSub.sourceCode) || code; if (!hasCode) { const vals = document.querySelectorAll('.value.lfe-caption'); if (vals.length >= 2 && vals[1].textContent !== '-') vals[1].textContent = '-'; const ce = document.querySelector('pre code'); if (ce) { const pre = ce.closest('pre'); if (pre) pre.outerHTML = `
本代码非 GenGen RMJ 提交,无法查看
前往原站
`; } } const card = document.querySelectorAll('.l-card')[1]; if (card && !card.querySelector('.rmj-orig-link')) { const a = document.createElement('a'); a.href = origUrl; a.target = '_blank'; a.className = 'rmj-orig-link'; a.textContent = '前往原站'; a.style.cssText = 'display:block;text-align:center;margin-top:16px;color:#3498db;text-decoration:none;'; card.appendChild(a); } if (currentSub && currentSub.verdict !== 'COMPILATION_ERROR') { const tcs = document.querySelectorAll('.test-case'); if (currentSub.isAT) { tcs.forEach((tc, idx) => { const idEl = tc.querySelector('.id'); const target = `#${idx + 1}`; if (idEl && idEl.textContent !== target) idEl.textContent = target; }); } else { const passed = currentSub.passedTestCount ?? 0; const idNum = currentSub.verdict === 'OK' ? (passed > 0 ? passed : 1) : passed + 1; const target = `#${idNum}`; tcs.forEach((tc) => { const idEl = tc.querySelector('.id'); if (idEl && idEl.textContent !== target) idEl.textContent = target; }); } } // AT 未返回完整测试点时的等待提示 if (atWaitPollingId !== null) { const h5 = document.querySelector( '#app > div.main-container.lside-nav > div > main > div > div > div.main > div > section > div > h5' ); if (h5 && h5.textContent !== '正在等待 AtCoder 评测完成') { h5.textContent = '正在等待 AtCoder 评测完成'; } const testWrap = document.querySelector('.test-case-wrap'); if (testWrap && !document.getElementById('rmj-at-wait-msg')) { const waitMsg = document.createElement('div'); waitMsg.id = 'rmj-at-wait-msg'; waitMsg.textContent = '正在等待 AtCoder 完整评测完毕'; waitMsg.style.cssText = 'text-align:center;padding:12px;color:#666;font-size:14px;'; testWrap.appendChild(waitMsg); } } }, 500); if ((!isCFPage && !isATPage) || !sub) return; const startAtWaitPolling = () => { if (atWaitPollingId !== null || !isATPage) return; atWaitPollingId = timers.iv(async () => { if (myGen !== generation) return; try { const s = await findAT(); if (myGen !== generation) return; if (s && s.cases.length > 0) { timers.clear(atWaitPollingId); atWaitPollingId = null; const msg = document.getElementById('rmj-at-wait-msg'); if (msg) msg.remove(); currentSub = s; await renderPage(s); updateCache(s); } } catch (e) { /* ignore */ } }, 5000); }; if (isATPage && sub.cases.length === 0 && sub.verdict !== 'COMPILATION_ERROR' && FINAL_CF.has(sub.verdict)) { startAtWaitPolling(); } if (!FINAL_CF.has(sub.verdict)) { let polling = false; const intervalId = timers.iv(async () => { if (myGen !== generation) return; if (polling) return; polling = true; try { const s = isCFPage ? await findCF() : await findAT(); if (myGen !== generation) return; if (s) { currentSub = s; updateCache(s); if (FINAL_CF.has(s.verdict) && s.isAT && s.cases.length === 0 && s.verdict !== 'COMPILATION_ERROR') { startAtWaitPolling(); } if (FINAL_CF.has(s.verdict)) { timers.clear(intervalId); await renderPage(s); } } } catch (e) { /* ignore */ } finally { polling = false; } }, 3000); } })(); }; return { init, destroy }; })(); // 洛谷记录列表页 const RecordList = (() => { let syncStarted = false; let pageKey = ''; let pageGen = 0; const timers = Timers(); const req = (url, opts = {}) => new Promise((res, rej) => { const t = setTimeout(() => rej(new Error('请求超时')), 8000); GM_xmlhttpRequest({ method: 'GET', url, ...opts, onload: (r) => { clearTimeout(t); res(r); }, onerror: (e) => { clearTimeout(t); rej(e); } }); }); const getCache = (key) => { try { const p = JSON.parse(GM_getValue(key, '[]')); if (!Array.isArray(p)) return []; return p.map((s) => { if (s.submitUrl) { let m = s.submitUrl.match(/atcoder\.jp\/contests\/([^/]+)\/submissions\/(\d+)/); if (m) s.submitUrl = `https://www.luogu.com.cn/record/at/${m[1]}/${m[2]}`; else { m = s.submitUrl.match(/codeforces\.com\/(?:contest|gym)\/(\d+)\/submission\/(\d+)/); if (m) s.submitUrl = `https://www.luogu.com.cn/record/cf/${m[1]}/${m[2]}`; } } if (s.luoguUrl) { let m = s.luoguUrl.match(/atcoder\.jp\/contests\/[^/]+\/tasks\/([^/\s?#]+)/); if (m) s.luoguUrl = `https://www.luogu.com.cn/problem/AT_${m[1]}`; else { m = s.luoguUrl.match(/codeforces\.com\/(?:problemset\/problem|(?:contest|gym)\/\d+\/problem)\/(\d+)\/([A-Za-z]\d?)/); if (m) s.luoguUrl = `https://www.luogu.com.cn/problem/CF${m[1]}${m[2]}`; } } return s; }); } catch { return []; } }; const saveCache = (key, c) => GM_setValue(key, JSON.stringify(Array.isArray(c) ? c : [])); const markUpdated = (key) => GM_setValue(key, Date.now()); const crawlATContest = async (contestId, existingCache = []) => { if (!contestId) return existingCache; try { const r = await req(`https://atcoder.jp/contests/${contestId}/submissions/me`); if (r.status !== 200) return existingCache; const doc = new DOMParser().parseFromString(r.responseText, 'text/html'); const rows = Array.from(doc.querySelectorAll('table.table tbody tr')); if (!rows.length) return existingCache; const cacheMap = new Map(existingCache.map((s) => [s.id, s])); let hasNew = false; for (const row of rows) { const cells = Array.from(row.querySelectorAll('td')); if (cells.length < 7) continue; const tl = cells[1]?.querySelector('a'); const tHref = tl?.href || ''; const tName = tl?.innerText?.trim() || ''; const tKey = tHref.match(/\/tasks\/([^/\s]+)/)?.[1] || ''; let rs = 'Waiting'; for (const c of cells) { const l = c.querySelector('.label'); if (l) { rs = l.innerText.trim(); break; } } const da = cells[cells.length - 1]?.querySelector('a.submission-details-link'); if (!da?.href) continue; const sid = da.href.split('/').pop(); const isF = FINAL_AT.has(rs); const prev = cacheMap.get(sid); if (!prev || prev.status !== rs) { cacheMap.set(sid, { id: sid, taskKey: tKey, taskDisplay: `${tKey} - ${tName}`, status: rs, submitUrl: `https://www.luogu.com.cn/record/at/${contestId}/${sid}`, luoguUrl: `https://www.luogu.com.cn/problem/AT_${tKey}`, timestamp: prev?.timestamp || Date.now(), isFinal: isF }); hasNew = true; } else if (isF && !prev.isFinal) { prev.isFinal = true; prev.status = rs; hasNew = true; } } if (hasNew) { return Array.from(cacheMap.values()).sort((a, b) => Number(b.id) - Number(a.id)); } return existingCache; } catch (e) { LOG('AT 爬取失败', e.message || e); return existingCache; } }; const fetchCFData = async (handle) => { const sr = await req(`https://codeforces.com/api/user.status?handle=${encodeURIComponent(handle)}&from=1`); if (sr.status !== 200) throw new Error('CF API 状态码非 200'); const d = JSON.parse(sr.responseText); if (d.status !== 'OK') throw new Error('CF API 状态非 OK'); const seen = new Set(); return (d.result || []).filter((s) => { if (seen.has(s.id)) return false; seen.add(s.id); return true; }).map((s) => ({ id: String(s.id), taskDisplay: `CF${s.contestId}${s.problem.index} - ${s.problem.name}`, status: s.verdict || 'Waiting', timestamp: s.creationTimeSeconds * 1000, submitUrl: `https://www.luogu.com.cn/record/cf/${s.contestId}/${s.id}`, luoguUrl: `https://www.luogu.com.cn/problem/CF${s.contestId}${s.problem.index}`, passedTestCount: s.passedTestCount })); }; const fetchATProblems = async () => { try { const r = await req('https://kenkoooo.com/atcoder/resources/problems.json'); if (r.status !== 200) return new Map(); const m = new Map(); for (const p of JSON.parse(r.responseText)) { if (p.id) m.set(p.id, { index: p.problem_index || '', name: p.name || '' }); } return m; } catch { return new Map(); } }; const fmtATTask = (pid, pMap) => { const p = pMap.get(pid); if (p?.name && p.index) return `${pid} - ${p.index} - ${p.name}`; const parts = pid.split('_'); const c = parts[0] || ''; const i = parts[1] ? parts[1].toUpperCase() : ''; return `${pid} - ${i ? `${c.toUpperCase()} ${i}` : pid}`; }; const fetchATSubmissions = async (pMap, uid) => { try { if (!uid) return []; const sr = await req(`https://kenkoooo.com/atcoder/atcoder-api/v3/user/submissions?user=${encodeURIComponent(uid)}&from_second=0`); if (sr.status !== 200) return []; const subs = JSON.parse(sr.responseText); if (!Array.isArray(subs)) return []; return subs.map((s) => ({ id: String(s.id), taskKey: s.problem_id, taskDisplay: fmtATTask(s.problem_id, pMap), status: s.result, submitUrl: `https://www.luogu.com.cn/record/at/${s.contest_id}/${s.id}`, luoguUrl: `https://www.luogu.com.cn/problem/AT_${s.problem_id}`, timestamp: s.epoch_second * 1000, isFinal: FINAL_AT.has(s.result) })); } catch (e) { LOG('Kenkoooo 拉取失败', e); return []; } }; const fetchATData = async (uid) => { const pMap = await fetchATProblems(); const subs = await fetchATSubmissions(pMap, uid); return subs.map((s) => ({ ...s, taskDisplay: fmtATTask(s.taskKey, pMap) })); }; let isCFSyncing = false; let isATSyncing = false; const syncCF = async (options = {}) => { if (isCFSyncing) return; isCFSyncing = true; try { const { render = false, renderFn = null, mainDiv = null, force = false } = options; const cache = getCache(CACHE_CF); const lastUpdate = GM_getValue(LAST_UPDATE_CF, 0); const isStale = (Date.now() - lastUpdate) > SYNC_INTERVAL_MS; if (!force && cache.length > 0 && !isStale) { if (render && renderFn && mainDiv) renderFn(cache, mainDiv); return; } const handle = GM_getValue('cf-handle', ''); if (!handle) { LOG('未找到 CF 用户名'); return; } const subs = await fetchCFData(handle); saveCache(CACHE_CF, subs); markUpdated(LAST_UPDATE_CF); if (render && renderFn && mainDiv) renderFn(subs, mainDiv); } catch (e) { LOG('CF 同步失败', e.message || e); } finally { isCFSyncing = false; } }; const syncAT = async (options = {}) => { if (isATSyncing) return; isATSyncing = true; try { const { render = false, renderFn = null, mainDiv = null, force = false } = options; const cache = getCache(CACHE_AT); const lastUpdate = GM_getValue(LAST_UPDATE_AT, 0); const isStale = (Date.now() - lastUpdate) > SYNC_INTERVAL_MS; if (!force && cache.length > 0 && !isStale) { if (render && renderFn && mainDiv) renderFn(cache, mainDiv); return; } const atHandle = GM_getValue('at-handle', ''); if (!atHandle) { LOG('未找到 AT 用户名'); return; } const subs = await fetchATData(atHandle); const mm = new Map(); for (const s of [...subs, ...cache]) { if (!mm.has(s.id)) mm.set(s.id, s); } const merged = Array.from(mm.values()).sort((a, b) => Number(b.id) - Number(a.id)); saveCache(CACHE_AT, merged); markUpdated(LAST_UPDATE_AT); if (render && renderFn && mainDiv) renderFn(merged, mainDiv); } catch (e) { LOG('AT 同步失败', e.message || e); } finally { isATSyncing = false; } }; const startBackgroundSync = () => { if (syncStarted) return; syncStarted = true; let disableSync = GM_getValue('rmj-disable-sync', false) === true; try { const params = new URLSearchParams(location.search); if (params.get('rmj-disable-sync') === 'true') disableSync = true; } catch { /* ignore */ } if (!disableSync) { syncCF({ render: false }); syncAT({ render: false }); } else { LOG('同步已禁用,仅显示模式'); } }; const destroy = () => { pageGen++; timers.clearAll(); pageKey = ''; }; const init = async () => { if (!isLuogu()) return; startBackgroundSync(); const URL_PATH = location.pathname; if (!/^\/record\/list/.test(URL_PATH)) { if (pageKey) destroy(); return; } const params = new URLSearchParams(window.location.search); const rmj = params.get('rmj'); if (!['1', '2'].includes(rmj)) { if (pageKey) destroy(); return; } const key = `${URL_PATH}?rmj=${rmj}`; if (pageKey === key) return; destroy(); pageKey = key; const myGen = pageGen; const pid = params.get('pid') || ''; const contest = params.get('contest') || ''; const isCFMode = rmj === '1'; const isATMode = rmj === '2'; const disableSync = GM_getValue('rmj-disable-sync', false) === true || params.get('rmj-disable-sync') === 'true'; const STATUS_MAP = isCFMode ? { OK: ['Accepted', 'cf-status-accepted'], WRONG_ANSWER: ['Wrong Answer', 'cf-status-wrong'], COMPILATION_ERROR: ['Compilation Error', 'cf-status-ce'], RUNTIME_ERROR: ['Runtime Error', 'cf-status-re'], TIME_LIMIT_EXCEEDED: ['Time Limit Exceeded', 'cf-status-limit'], MEMORY_LIMIT_EXCEEDED: ['Memory Limit Exceeded', 'cf-status-limit'], OUTPUT_LIMIT_EXCEEDED: ['Output Limit Exceeded', 'cf-status-limit'], TESTING: ['Running', 'cf-status-running'] } : { AC: ['Accepted', 'cf-status-accepted'], WA: ['Wrong Answer', 'cf-status-wrong'], CE: ['Compilation Error', 'cf-status-ce'], RE: ['Runtime Error', 'cf-status-re'], TLE: ['Time Limit Exceeded', 'cf-status-limit'], MLE: ['Memory Limit Exceeded', 'cf-status-limit'], OLE: ['Output Limit Exceeded', 'cf-status-limit'], IE: ['Internal Error', 'cf-status-ce'] }; const style = document.createElement('style'); style.textContent = ` .cf-card{display:flex;align-items:center;font-size:16px;padding:10px 0;border-bottom:1px solid #eee} .cf-avatar img{width:35px;height:35px;border-radius:50%;margin-right:12px} .cf-nameblock{display:flex;flex-direction:column;width:180px;margin-right:12px} .cf-name{font-weight:bold;color:#333;font-size:16px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} .cf-time{font-size:14px;color:#888;line-height:1.2;margin-top:2px;white-space:nowrap} .cf-status{flex:0 0 auto;margin-right:12px;text-align:left;width:200px} .cf-status span{border-radius:2px;padding:2px 3px;font-size:14px;font-weight:600;white-space:nowrap;display:inline-flex;align-items:center;position:relative;max-width:100%;overflow:hidden;text-overflow:ellipsis;box-sizing:border-box} .cf-status-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap} .cf-status-subtext{position:absolute;top:-2px;right:2px;font-size:10px!important;color:rgba(255,255,255,.8);font-weight:bold;line-height:1;pointer-events:none} .cf-status-accepted{background:rgb(82,196,26);color:#fff} .cf-status-wrong{background:rgb(231,76,60);color:#fff} .cf-status-re{background:rgb(157,61,207);color:#fff} .cf-status-ce{background:rgb(250,219,20);color:#fff} .cf-status-limit{background:rgb(5,34,66);color:#fff} .cf-status-running{background:#888;color:#fff} .cf-status-other{background:rgb(243,156,17);color:#fff} .cf-problem{flex:1;color:rgb(52,152,219);font-size:16px;text-align:left;min-width:100px;margin-left:60px} .cf-error{padding:10px;color:#e74c3c;font-weight:bold} .cf-loading{padding:10px;color:#708090;font-weight:bold} `; document.head.appendChild(style); const getUser = () => { const ne = document.querySelector("#app > div.user-nav.rside.hide.nav-scrollbar > div.title > div > span") || document.querySelector("#app > div.user-nav.rside.show.nav-scrollbar > div.title > div > span"); const ae = document.querySelector("#app > div.top-bar > div.right > div > span.avatar > a > img"); return { name: (ne?.textContent || 'User').trim(), color: ne?.style?.color || '#333', avatar: (ae?.src || 'https://cdn.luogu.com.cn/upload/usericon/default.png').trim() }; }; const fmtVerdict = (v) => STATUS_MAP[v] || [ v.split('_').map((w) => w[0] + w.slice(1).toLowerCase()).join(' '), 'cf-status-other' ]; const createCard = (sub) => { const card = document.createElement('div'); card.className = 'cf-card'; if (sub.id) card.dataset.id = sub.id; const u = getUser(); const av = document.createElement('div'); av.className = 'cf-avatar'; const img = document.createElement('img'); img.src = u.avatar; img.onerror = () => img.src = 'https://cdn.luogu.com.cn/upload/usericon/1.png'; av.appendChild(img); const nb = document.createElement('div'); nb.className = 'cf-nameblock'; const nd = document.createElement('div'); nd.className = 'cf-name'; nd.style.color = u.color; nd.textContent = u.name; const td = document.createElement('div'); td.className = 'cf-time'; td.textContent = sub.timeText; nb.append(nd, td); const sd = document.createElement('div'); sd.className = 'cf-status'; const sl = document.createElement('a'); sl.href = sub.submitUrl; sl.target = '_blank'; sl.rel = 'noopener noreferrer'; const sp = document.createElement('span'); const [full, cls] = (isCFMode || sub.isFinal) ? fmtVerdict(sub.status) : ['Running', 'cf-status-running']; sp.className = cls; const ts = document.createElement('span'); ts.className = 'cf-status-text'; ts.textContent = full; sp.appendChild(ts); if (isCFMode && sub.passedTestCount != null && !isNaN(sub.passedTestCount)) { const st = document.createElement('span'); st.className = 'cf-status-subtext'; st.textContent = String((full === 'AC' || full === 'Accepted') ? sub.passedTestCount : sub.passedTestCount + 1); sp.appendChild(st); } sl.appendChild(sp); sd.appendChild(sl); const pd = document.createElement('div'); pd.className = 'cf-problem'; const pl = document.createElement('a'); pl.href = sub.luoguUrl; pl.textContent = sub.taskDisplay; pl.target = '_blank'; pl.rel = 'noopener noreferrer'; pd.appendChild(pl); card.append(av, nb, sd, pd); return card; }; const CACHE_KEY = isCFMode ? CACHE_CF : CACHE_AT; const getLocalCache = () => getCache(CACHE_KEY); const saveLocalCache = (c) => saveCache(CACHE_KEY, c); const updCount = (n) => { const s = document.querySelector('.number'); if (s) s.textContent = String(n || 0); }; let searchFilter = pid; const render = (cache, mainDiv) => { if (myGen !== pageGen) return; let filtered = cache; if (searchFilter) { const q = isCFMode ? searchFilter.toLowerCase() : searchFilter.replace(/^AT_/i, '').toLowerCase(); filtered = cache.filter((s) => s.taskDisplay?.toLowerCase().includes(q)); } mainDiv.innerHTML = ''; updCount(filtered.length); if (!filtered.length) { const msg = document.createElement('div'); msg.className = 'cf-loading'; msg.textContent = ''; mainDiv.appendChild(msg); } else { filtered.forEach((s) => { mainDiv.appendChild(createCard({ ...s, timeText: new Date(s.timestamp || 0).toLocaleString('zh-CN') })); }); } }; const bindSearch = async (mainDiv) => { try { const input = await waitForEl( '#app > div.main-container.lside-nav > div > main > div > div:nth-child(1) > section:nth-child(1) > div > input:nth-child(1)', 8000 ); if (myGen !== pageGen) return; input.value = searchFilter; const orig = input.oninput; input.oninput = (e) => { searchFilter = e.target.value.trim(); const url = new URL(window.location); if (searchFilter) url.searchParams.set('pid', searchFilter); else url.searchParams.delete('pid'); history.replaceState(null, '', url); render(getLocalCache(), mainDiv); orig?.call(input, e); }; } catch { LOG('未找到搜索框'); } }; let mainDiv; try { mainDiv = await waitForEl( '#app > div.main-container.lside-nav > div > main > div > div:nth-child(2)', 10000 ); } catch (e) { LOG('记录列表容器未找到', e); return; } if (myGen !== pageGen) return; render(getLocalCache(), mainDiv); await bindSearch(mainDiv); if (myGen !== pageGen) return; if (isCFMode) { if (!disableSync) { let isFetching = false; const updateNow = async () => { if (myGen !== pageGen) return; if (isFetching) return; isFetching = true; try { const handle = GM_getValue('cf-handle', ''); if (!handle) return; const sr = await req(`https://codeforces.com/api/user.status?handle=${encodeURIComponent(handle)}&from=1`); if (myGen !== pageGen) return; if (sr.status !== 200) return; const d = JSON.parse(sr.responseText); if (d.status !== 'OK') return; const seen = new Set(); const subs = (d.result || []).filter((s) => { if (seen.has(s.id)) return false; seen.add(s.id); return true; }).map((s) => ({ id: String(s.id), taskDisplay: `CF${s.contestId}${s.problem.index} - ${s.problem.name}`, status: s.verdict || 'Waiting', timestamp: s.creationTimeSeconds * 1000, submitUrl: `https://www.luogu.com.cn/record/cf/${s.contestId}/${s.id}`, luoguUrl: `https://www.luogu.com.cn/problem/CF${s.contestId}${s.problem.index}`, passedTestCount: s.passedTestCount })); if (myGen !== pageGen) return; saveLocalCache(subs); markUpdated(LAST_UPDATE_CF); render(subs, mainDiv); } catch (e) { LOG('CF 列表更新失败', e.message || e); } finally { isFetching = false; } }; updateNow(); timers.iv(updateNow, 6000); } } else { let cache = getLocalCache(); if (!disableSync) { (async () => { try { const atHandle = GM_getValue('at-handle', ''); if (!atHandle) return; const pMap = await fetchATProblems(); const kSubs = await fetchATSubmissions(pMap, atHandle); if (myGen !== pageGen) return; const enhanced = kSubs.map((s) => ({ ...s, taskDisplay: fmtATTask(s.taskKey, pMap) })); const mm = new Map(); for (const s of [...enhanced, ...cache]) { if (!mm.has(s.id)) mm.set(s.id, s); } cache = Array.from(mm.values()).sort((a, b) => Number(b.id) - Number(a.id)); saveLocalCache(cache); markUpdated(LAST_UPDATE_AT); render(cache, mainDiv); } catch (e) { LOG('AT 后台加载失败', e); } })(); if (contest) { timers.iv(async () => { if (myGen !== pageGen) return; const prev = getLocalCache(); const updated = await crawlATContest(contest, prev); if (myGen !== pageGen) return; if (updated !== prev) { cache = updated; saveLocalCache(cache); markUpdated(LAST_UPDATE_AT); render(cache, mainDiv); } }, 6000); } } } }; return { init, destroy }; })(); // CF/AT 盾提交 const ShieldSubmit = (() => { const VALID = [ /^https:\/\/www\.luogu\.com\.cn\/problem\/(AT|CF).+/, /^https:\/\/codeforces\.com\/problemset\/status\?my=on$/, /^https:\/\/atcoder\.jp\/contests\/[^/]+\/submit\?RMJ=1$/, /^https:\/\/atcoder\.jp\/contests\/[^/]+\/submissions\/me$/, /^https:\/\/codeforces\.com\/problemset\/submit\/.+/, ]; const KEYS = ['code', 'contest', 'task', 'back', 'platform', 'pid', 'cf_contest', 'cf_index', 'body_back', 'bg_image', 'bg_b64', 'bg_color', 'bg_repeat', 'bg_size', 'bg_pos', 'mid_mask', 'color_filter', 'card_bg', 'card_blur']; const P = 'gen-at-'; const set = (k, v) => GM_setValue(P + k, String(v)); const get = (k) => GM_getValue(P + k, ''); const clr = () => KEYS.forEach((k) => GM_setValue(P + k, '')); const LIMIT = 4e6; const LOGO = 'https://cdn.luogu.com.cn/upload/image_hosting/yy27itbl.png'; const setFlag = (k, v) => GM_setValue('rmj-flag-' + k, String(v)); const getFlag = (k, def) => GM_getValue('rmj-flag-' + k, def); const clearFlags = () => GM_setValue('rmj-flag-action', ''); let submitting = false; let luoguSetupDone = false; let cfAtSetupDone = false; const stripUrl = (v) => { const m = (v || '').match(/url\(\s*(['"]?)(.+?)\1\s*\)/i); return m ? m[2] : v; }; const showRedirectMask = () => { if (document.getElementById('rmj-redirect-mask')) return; const m = document.createElement('div'); m.id = 'rmj-redirect-mask'; m.style.cssText = 'position:fixed!important;inset:0!important;z-index:2147483647!important;background:rgb(239,239,239)!important;pointer-events:all!important;'; const attach = () => (document.body || document.documentElement).appendChild(m); if (document.body) attach(); else new MutationObserver((_, o) => { if (document.body) { o.disconnect(); attach(); } }) .observe(document.documentElement, { childList: true }); }; GM_addStyle(` .swal2-loader{visibility:hidden} #rmj-overlay{ position:fixed!important;inset:0!important;z-index:999998!important; display:flex!important;align-items:center!important;justify-content:center!important; overflow:hidden!important;pointer-events:all!important;cursor:default!important; background:var(--rmj-back,#f5f5f5)!important; color:#333!important;box-sizing:border-box!important;-webkit-font-smoothing:antialiased!important; } #rmj-overlay *{box-sizing:border-box!important;margin:0!important;padding:0!important;font-family:inherit!important;line-height:inherit!important} #rmj-overlay .ly{position:absolute!important;inset:0!important;pointer-events:none!important; -webkit-mask:var(--rmj-mask,none) no-repeat/100% 100%!important;mask:var(--rmj-mask,none) no-repeat/100% 100%!important} #rmj-overlay .ly-c{background:var(--rmj-color,transparent)!important;filter:var(--rmj-filter,none)!important} #rmj-overlay .ly-i{background:var(--rmj-img,none) var(--rmj-pos,center)/var(--rmj-size,cover) var(--rmj-repeat,no-repeat)!important} #rmj-card{ position:relative!important;z-index:1!important;width:420px!important;max-width:90vw!important; padding:36px 32px 40px!important;border-radius:4px!important;border:none!important; background:var(--rmj-card,rgba(255,255,255,.78))!important; backdrop-filter:var(--rmj-blur,blur(14px))!important;-webkit-backdrop-filter:var(--rmj-blur,blur(14px))!important; box-shadow:0 8px 32px rgba(0,0,0,.18),0 1.5px 6px rgba(0,0,0,.08)!important; display:flex!important;flex-direction:column!important;align-items:center!important;gap:20px!important; } #rmj-overlay.anim #rmj-card{opacity:0!important;transform:translateY(8px)!important;transition:opacity .45s ease .15s,transform .45s ease .15s!important} #rmj-overlay.anim.open #rmj-card{opacity:1!important;transform:translateY(0)!important} #rmj-card h2{font-size:20px!important;font-weight:600!important;color:#333!important;letter-spacing:.5px!important;user-select:none!important} #rmj-card p{font-size:13px!important;color:#888!important;margin-top:-10px!important;text-align:center!important;user-select:none!important} #rmj-slot{width:100%!important;min-height:78px!important;display:flex!important;align-items:center!important;justify-content:center!important} #rmj-slot:empty::after{content:'等待验证组件加载…';font-size:13px;color:#aaa} #rmj-spin{width:26px;height:26px;border:3px solid rgba(0,0,0,.1);border-top-color:#005c97;border-radius:50%;animation:sp .7s linear infinite} #rmj-error{color:#d32f2f!important;font-size:13px!important;text-align:center!important;word-break:break-all!important;max-height:120px!important;overflow-y:auto!important;padding:8px 12px!important;background:rgba(211,47,47,.08)!important;border-radius:4px!important;width:100%!important;display:none!important} @keyframes sp{to{transform:rotate(360deg)}} body.rmj-lock{overflow:hidden!important;height:100vh!important} `); const toB64 = (raw) => new Promise((res, rej) => { const url = stripUrl(raw); if (!/^https?:\/\//i.test(url || '')) return res(''); const img = new Image(); img.crossOrigin = 'anonymous'; img.onload = () => { try { const cv = document.createElement('canvas'); const MAX = 1920; let w = img.naturalWidth, h = img.naturalHeight; if (Math.max(w, h) > MAX) { const s = MAX / Math.max(w, h); w = w * s | 0; h = h * s | 0; } cv.width = w; cv.height = h; cv.getContext('2d').drawImage(img, 0, 0, w, h); let q = 0.9, b = ''; while (q >= 0.1) { b = cv.toDataURL('image/jpeg', q); if (b.length <= LIMIT) break; q = +(q - 0.1).toFixed(1); } if (b.length > LIMIT) { const c2 = document.createElement('canvas'); c2.width = w >> 1; c2.height = h >> 1; c2.getContext('2d').drawImage(cv, 0, 0, c2.width, c2.height); b = c2.toDataURL('image/jpeg', 0.5); } if (b.length > LIMIT) { const c3 = document.createElement('canvas'); c3.width = w >> 2; c3.height = h >> 2; c3.getContext('2d').drawImage(cv, 0, 0, c3.width, c3.height); b = c3.toDataURL('image/jpeg', 0.4); } res(b.length <= LIMIT ? b : ''); } catch (e) { rej(e); } }; img.onerror = () => rej(new Error('图片加载失败')); img.src = url; }); const applyVars = (el) => ThemeStore.applyTo(el); const overlay = (anim, ox, oy) => { document.documentElement.style.overflow = 'hidden'; let el = document.getElementById('rmj-overlay'); if (el) return el; el = document.createElement('div'); el.id = 'rmj-overlay'; el.innerHTML = `

正在加载验证

通过 Cloudflare 安全验证后将继续提交

`; applyVars(el); const mount = () => { document.body.appendChild(el); if (!anim) return; ox ??= innerWidth / 2; oy ??= innerHeight / 2; const R = Math.hypot(innerWidth, innerHeight) + 60; el.classList.add('anim'); el.style.clipPath = `circle(0px at ${ox}px ${oy}px)`; requestAnimationFrame(() => requestAnimationFrame(() => { el.style.transition = 'clip-path .6s cubic-bezier(.22,1,.36,1)'; el.style.clipPath = `circle(${R}px at ${ox}px ${oy}px)`; el.classList.add('open'); })); }; document.body ? mount() : new MutationObserver((_, o) => { if (document.body) { o.disconnect(); mount(); } }) .observe(document.documentElement, { childList: true }); return el; }; const showError = (msg) => { const errEl = document.getElementById('rmj-error'); const slot = document.getElementById('rmj-slot'); const title = document.querySelector('#rmj-card h2'); if (errEl) { errEl.textContent = msg; errEl.style.display = 'block'; } if (slot) slot.innerHTML = ''; if (title) title.textContent = '操作失败,请检查你的远程 OJ 状态'; ERR(msg); }; const onBody = (cb) => document.body ? cb() : new MutationObserver((_, o) => { if (document.body) { o.disconnect(); cb(); } }) .observe(document.documentElement, { childList: true }); const setupMeta = (plat) => { document.title = plat === 'codeforces' ? 'GenGen RMJ on CF' : 'GenGen RMJ on AT'; const fixIcon = () => { document.querySelectorAll('link[rel*="icon"]').forEach((l) => l.remove()); const h = document.head; for (const s of ['16x16', '32x32', '48x48', '192x192']) { const l = document.createElement('link'); l.rel = 'icon'; l.type = 'image/png'; l.href = LOGO; l.sizes = s; h.appendChild(l); } const a = document.createElement('link'); a.rel = 'apple-touch-icon'; a.href = LOGO; a.sizes = '180x180'; h.appendChild(a); }; const guard = () => new MutationObserver((muts) => { for (const m of muts) { for (const n of m.addedNodes) { if (n.nodeType === 1 && n.tagName === 'LINK' && /icon/i.test(n.rel) && n.href !== LOGO) { n.remove(); fixIcon(); return; } } } }).observe(document.head, { childList: true, subtree: true }); const noTrans = () => { const h = document.documentElement; h.classList.add('notranslate'); h.setAttribute('translate', 'no'); h.setAttribute('lang', 'zh-CN'); const add = (a) => { const m = document.createElement('meta'); Object.entries(a).forEach(([k, v]) => m.setAttribute(k, v)); document.head.appendChild(m); }; add({ name: 'google', content: 'notranslate' }); add({ 'http-equiv': 'Content-Language', content: 'zh-CN' }); add({ name: 'msapplication-config', content: 'none' }); }; const run = () => { fixIcon(); guard(); noTrans(); }; document.head ? run() : new MutationObserver((_, o) => { if (document.head) { o.disconnect(); run(); } }) .observe(document.documentElement, { childList: true, subtree: true }); }; const SUBMIT_RE = /\/fe\/api\/problem\/submit\/([^/?#]+)/; const setupLuogu = () => { if (luoguSetupDone) return; if (!location.href.includes('luogu.com.cn')) return; luoguSetupDone = true; let click = { x: innerWidth / 2, y: innerHeight / 2 }; document.addEventListener('click', (e) => { click = { x: e.clientX, y: e.clientY }; }, true); const pidTarget = (raw) => { const p = String(raw).trim(); let m = p.match(/^CF(\d+)([A-Z]\d*)$/); if (m) return { plat: 'codeforces', contest: m[1], index: m[2] }; m = p.match(/^AT_([a-z0-9]+)_(.+)$/i); if (m) return { plat: 'atcoder', contest: m[1], task: `${m[1]}_${m[2]}` }; return null; }; const atFromDOM = () => { for (const a of document.querySelectorAll('a[href*="atcoder.jp/contests/"]')) { const m = a.href.match(/atcoder\.jp\/contests\/([^/]+)\/tasks\/([^/?#]+)/); if (m) return { plat: 'atcoder', contest: m[1], task: m[2] }; } return null; }; const intercept = async (code, pid) => { pid = String(pid).trim(); let t = pidTarget(pid); if (!t && /^AT/i.test(pid)) { await new Promise((r) => document.body ? r() : new MutationObserver((_, o) => { if (document.body) { o.disconnect(); r(); } }) .observe(document.documentElement, { childList: true })); for (let i = 0; i < 30 && !t; i++) { t = atFromDOM(); if (!t) await sleep(200); } } if (!t) return; // 未绑定:直接拒绝 const bindKey = t.plat === 'codeforces' ? 'cf-handle' : 'at-handle'; const bindName = t.plat === 'codeforces' ? 'Codeforces' : 'AtCoder'; if (!GM_getValue(bindKey, '')) { overlay(false); showError(`请先在 GenGen RMJ 面板中绑定 ${bindName} 账号`); return; } if (t.plat === 'codeforces' && (!/^\d+$/.test(t.contest) || !/^[A-Z]\d*$/.test(t.index))) { ERR(`CF 目标非法: ${t.contest}/${t.index}`); return; } ThemeStore.extractFromLuogu(); set('code', code + `\n\n// 本代码使用 GenGen RMJ 于 ${Date()} 提交 | ${pid}`); set('back', location.href); set('platform', t.plat); set('pid', pid); if (t.plat === 'codeforces') { set('cf_contest', t.contest); set('cf_index', t.index); } else { set('contest', t.contest); set('task', t.task); } // 只在成功时覆盖 bg_b64,避免清空旧数据 const bgRaw = get('bg_image'); if (bgRaw) { try { const b64 = await toB64(bgRaw); if (b64) set('bg_b64', b64); } catch { /* 保留旧值 */ } } overlay(true, click.x, click.y); const url = t.plat === 'codeforces' ? `https://codeforces.com/problemset/submit/${t.contest}/${t.index}?RMJ=1` : `https://atcoder.jp/contests/${t.contest}/submit?RMJ=1`; setTimeout(() => { location.href = url; }, 700); }; const XHR = unsafeWindow.XMLHttpRequest; const _open = XHR.prototype.open; const _send = XHR.prototype.send; XHR.prototype.open = function (m, u, ...r) { this._rmj = { m: m.toUpperCase(), u: String(u) }; return _open.call(this, m, u, ...r); }; XHR.prototype.send = function (body) { const m = this._rmj?.m === 'POST' && SUBMIT_RE.exec(this._rmj.u); if (m) { let code = ''; try { code = JSON.parse(String(body ?? '')).code || ''; } catch { /* ignore */ } if (code.trim()) intercept(code, m[1]); this.abort(); return; } return _send.call(this, body); }; }; const setupCfAt = () => { if (cfAtSetupDone) return; cfAtSetupDone = true; const h = location.href; const isFromRMJ = document.referrer.includes('RMJ=1') || getFlag('action'); if (h.includes('codeforces.com/problemset/status') && isFromRMJ) { showRedirectMask(); handleStatusPage('codeforces'); return; } if (h.includes('atcoder.jp/contests/') && h.includes('/submissions/me') && isFromRMJ) { showRedirectMask(); handleStatusPage('atcoder'); return; } onBody(async () => { if (h.includes('atcoder.jp') && h.includes('RMJ=1')) init('atcoder'); else if (h.includes('codeforces.com') && new URL(h).searchParams.get('RMJ') === '1') init('codeforces'); }); }; function init(plat) { setupMeta(plat); document.documentElement.classList.add('rmj-lock'); overlay(false); const go = () => submit(plat); document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', go) : go(); } async function handleStatusPage(plat) { let sid = null; for (let i = 0; i < 10; i++) { sid = await archiveCode(plat); if (sid) break; await sleep(1500); } clearFlags(); let targetUrl; if (sid) { if (plat === 'codeforces') { targetUrl = `https://www.luogu.com.cn/record/cf/${get('cf_contest')}/${sid}`; } else { targetUrl = `https://www.luogu.com.cn/record/at/${get('contest')}/${sid}`; } } else { if (plat === 'codeforces') { targetUrl = `https://codeforces.com/contest/${get('cf_contest')}/my`; } else { targetUrl = `https://atcoder.jp/contests/${get('contest')}/submissions/me`; } } location.href = targetUrl; } async function archiveCode(plat) { try { const c = plat === 'codeforces' ? get('cf_contest') : get('contest'); if (plat === 'codeforces') { const code = get('code'); if (!code) return; const my = `https://codeforces.com/contest/${c}/my`; const doc = new DOMParser().parseFromString( await (await fetch(my, { credentials: 'include' })).text(), 'text/html' ); const sid = (doc.querySelector('a[href*="/submission"]')?.href || '') .match(/submissions?\/(\d+)/)?.[1]; if (sid) GM_setValue(`{${c},${sid}}`, code); return sid; } else { const res = await fetch( `https://atcoder.jp/contests/${c}/submissions/me/status/json`, { credentials: 'include' } ); if (!res.ok) return null; const data = await res.json(); if (data && data.Result) { const ids = Object.keys(data.Result); if (ids.length > 0) { ids.sort((a, b) => Number(b) - Number(a)); return ids[0]; } } return null; } } catch (e) { ERR(e); } } async function submit(plat) { if (submitting) return; submitting = true; try { const code = get('code'); if (!code?.trim()) { showError('代码为空,请返回重新操作'); clr(); return; } document.documentElement.classList.remove('rmj-lock'); let form = null; for (let i = 0; i < 100; i++) { form = plat === 'atcoder' ? document.querySelector('form[action*="/submit"]') : document.querySelector('form.submitFrameForm') || document.querySelector('form.submit-form') || document.querySelector('form[method="post"][action*="csrf_token"]'); if (form) break; await sleep(100); } if (!form) { const blocked = /403|just a moment|attention required/i.test(document.title) || !!document.querySelector('#challenge-stage,#challenge-form,.cf-challenge,iframe[src*="challenges.cloudflare.com"]'); if (blocked) { showError('页面被 Cloudflare 拦截,请手动完成验证后刷新'); return; } showError('未找到提交表单'); clr(); return; } if (plat === 'atcoder') { const ta = document.getElementById('plain-textarea'); if (ta) { ta.value = code; ta.dispatchEvent(new Event('input', { bubbles: true })); } const ace = document.getElementById('editor') || document.querySelector('.ace_editor'); if (ace && unsafeWindow.ace) { try { const e = unsafeWindow.ace.edit(ace); e.setValue(code, 1); e.clearSelection(); } catch { /* ignore */ } } } else { const ta = document.querySelector('#sourceCodeTextarea'); if (ta) { ta.value = code; ta.dispatchEvent(new Event('input', { bubbles: true })); } const ace = document.getElementById('editor'); if (ace && unsafeWindow.ace) { try { const e = unsafeWindow.ace.edit(ace); e.setValue(code, 1); e.clearSelection(); } catch { /* ignore */ } } } if (plat === 'atcoder') { const task = get('task'); if (task) { const s = document.querySelector('select[name="data.TaskScreenName"]'); if (s) { s.value = task; s.dispatchEvent(new Event('change', { bubbles: true })); } } const l = document.querySelector('select[name="data.LanguageId"]'); if (l) { l.value = '6017'; l.dispatchEvent(new Event('change', { bubbles: true })); } } else { const l = document.querySelector('select[name="programTypeId"]'); if (l) { l.value = '91'; l.dispatchEvent(new Event('change', { bubbles: true })); } } const CF = '.cf-challenge,.cf-turnstile,iframe[src*="challenges.cloudflare.com"],[data-callback*="turnstile"]'; let hasCF = false; for (let i = 0; i < 30; i++) { if (document.querySelector(CF)) { hasCF = true; break; } await sleep(100); } let cfToken = ''; if (hasCF) { const cfWidget = document.querySelector(CF); const slot = document.getElementById('rmj-slot'); if (slot) { const updatePosition = () => { const rect = slot.getBoundingClientRect(); const w = 300, h = 65; const top = rect.top + (rect.height - h) / 2; const left = rect.left + (rect.width - w) / 2; cfWidget.style.position = 'fixed'; cfWidget.style.top = `${top}px`; cfWidget.style.left = `${left}px`; cfWidget.style.width = `${w}px`; cfWidget.style.height = `${h}px`; cfWidget.style.zIndex = '9999999'; cfWidget.style.margin = '0'; cfWidget.style.padding = '0'; }; updatePosition(); window.addEventListener('resize', updatePosition); } let tokenFound = false; for (let i = 0; ; i++) { const t = document.querySelector('input[name="turnstileToken"],input[name^="cf-turnstile-response"]'); if (t?.value?.length > 10) { cfToken = t.value; tokenFound = true; break; } await sleep(100); } if (!tokenFound) { showError('验证超时,请手动点击提交'); return; } } if (plat === 'codeforces' && hasCF) { setFlag('action', 'submit'); let submitBtn = form.querySelector('input[type="submit"], button[type="submit"]'); if (!submitBtn) submitBtn = form.querySelector('.submit-form-submit, .submit, [name="action"][value="submitSolutionFormSubmitted"]'); if (submitBtn) { submitBtn.click(); } else { HTMLFormElement.prototype.submit.call(form); } return; } const contest = get('contest'); const cfcon = get('cf_contest'); let url; if (plat === 'codeforces') { const csrf = form.querySelector('input[name="csrf_token"]')?.value || document.querySelector('meta[name="csrf_token"]')?.content || ''; url = location.origin + location.pathname; if (csrf) url += (url.includes('?') ? '&' : '?') + 'csrf_token=' + encodeURIComponent(csrf); } else { url = form.action || location.href; } const fd = new FormData(form); if (plat === 'codeforces') { fd.set('programTypeId', '91'); fd.set('source', code); } else if (plat === 'atcoder') { fd.set('sourceCode', code); } if (cfToken) { fd.set('cf-turnstile-response', cfToken); fd.set('turnstileToken', cfToken); } try { const r = await fetch(url, { method: 'POST', body: fd, credentials: 'include', redirect: 'manual', headers: { Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', Referer: location.href } }); const isSuccess = r.type === 'opaqueredirect' || r.status === 302 || r.status === 303 || r.ok; if (!isSuccess) throw new Error(`HTTP ${r.status}`); let sid = null; for (let i = 0; i < 8; i++) { await sleep(1000); sid = await archiveCode(plat); if (sid) break; } clr(); if (plat === 'codeforces') { location.href = `https://www.luogu.com.cn/record/cf/${cfcon}/${sid || ''}`; } else if (plat === 'atcoder') { location.href = `https://www.luogu.com.cn/record/at/${contest}/${sid || ''}`; } } catch (e) { showError(`提交失败:\n${e.message}`); clr(); } } finally { submitting = false; } } const run = () => { if (!VALID.some((r) => r.test(location.href))) return; setupLuogu(); setupCfAt(); }; return { init: run, showRedirectMask }; })(); // 主调度 const bootstrap = () => { CfChallengePatch.apply(); if (isCF()) { MainUI.captureCfHandle(); ShieldSubmit.init(); return; } if (isLuogu()) { MainUI.init(); RecordList.init(); RecordDetail.init(); ShieldSubmit.init(); ThemeStore.extractWithRetry(); Router.on(() => { MainUI.hideModal(); MainUI.handleRoute(); RecordList.init(); RecordDetail.init(); ShieldSubmit.init(); }); return; } if (isAT()) { ShieldSubmit.init(); return; } if (isCfChallenge()) return; }; docReady(bootstrap); })();