您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
北林OJ专用,替换C++头文件、展示当前题目可查看的答案、提交状态显示与CE原因显示、设置每页展示题目数、展示题目状态
// ==UserScript== // @name 北林OJ // @namespace gf.qytechs.cn // @match https://www.bjfuacm.com // @match https://www.bjfuacm.com/* // @grant unsafeWindow // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @run-at document-start // @version 1.0 // @author Gwen0x4c3 // @license MIT // @description 北林OJ专用,替换C++头文件、展示当前题目可查看的答案、提交状态显示与CE原因显示、设置每页展示题目数、展示题目状态 // ==/UserScript== !(function() { 'use strict'; var $msg = {success:console.log,error:console.log,info:console.log} let h0x00=setInterval(()=>{ if(document&&document.head&&document.body) { clearInterval(h0x00) function useMessage(){function n(n){for(var o=10,e=0;e<f.length;e++){var t=f[e];if(n&&n===t)break;o+=t.clientHeight+20}return o}function o(o){for(var e=0;e<f.length;e++){if(f[e]===o){f.splice(e,1);break}}o.classList.add(a.hide),f.forEach(function(o){o.style.top=n(o)+"px"})}function e(e){function i(){p.removeEventListener("animationend",i),setTimeout(o,x||t.duration||3e3,p)}function s(){"0"===getComputedStyle(p).opacity&&(p.removeEventListener("transitionend",s),p.remove())}var d=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"info",x=arguments[2],p=r.createElement("div");p.className=a.box+" "+d,p.style.top=n()+"px",p.style.zIndex=c,p.innerHTML='\n <span class="'+a.icon+'"></span>\n <span class="'+a.text+'">'+e+"</span>\n ",c++,f.push(p),r.body.appendChild(p),p.addEventListener("animationend",i),p.addEventListener("transitionend",s)}var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},r=document,i="__"+Math.random().toString(36).slice(2,7),a={box:"msg-box"+i,hide:"hide"+i,text:"msg-text"+i,icon:"msg-icon"+i},s=r.createElement("style");s.textContent=("\n ."+a.box+", ."+a.icon+", ."+a.text+" {\n padding: 0;\n margin: 0;\n box-sizing: border-box;\n }\n ."+a.box+" {\n position: fixed;\n top: 0;\n left: 50%;\n display: flex;\n padding: 12px 16px;\n border-radius: 2px;\n background-color: #fff;\n box-shadow: 0 3px 3px -2px rgba(0,0,0,.2),0 3px 4px 0 rgba(0,0,0,.14),0 1px 8px 0 rgba(0,0,0,.12);\n white-space: nowrap;\n animation: "+a.box+"-move .4s;\n transition: .4s all;\n transform: translate3d(-50%, 0%, 0);\n opacity: 1;\n overflow: hidden;\n }\n ."+a.box+'::after {\n content: "";\n position: absolute;\n left: 0;\n top: 0;\n height: 100%;\n width: 4px;\n }\n @keyframes '+a.box+"-move {\n 0% {\n opacity: 0;\n transform: translate3d(-50%, -100%, 0);\n }\n 100% {\n opacity: 1;\n transform: translate3d(-50%, 0%, 0);\n }\n }\n ."+a.box+"."+a.hide+" {\n opacity: 0;\n /* transform: translate3d(-50%, -100%, 0); */\n transform: translate3d(-50%, -100%, 0) scale(0);\n }\n ."+a.icon+" {\n display: inline-block;\n width: 18px;\n height: 18px;\n border-radius: 50%;\n overflow: hidden;\n margin-right: 6px;\n position: relative;\n }\n ."+a.text+" {\n font-size: 14px;\n line-height: 18px;\n color: #555;\n }\n ."+a.icon+"::after,\n ."+a.icon+'::before {\n position: absolute;\n content: "";\n background-color: #fff;\n }\n .'+a.box+".info ."+a.icon+", ."+a.box+".info::after {\n background-color: #1890ff;\n }\n ."+a.box+".success ."+a.icon+", ."+a.box+".success::after {\n background-color: #52c41a;\n }\n ."+a.box+".warning ."+a.icon+", ."+a.box+".warning::after {\n background-color: #faad14;\n }\n ."+a.box+".error ."+a.icon+", ."+a.box+".error::after {\n background-color: #ff4d4f;\n }\n ."+a.box+".info ."+a.icon+"::after,\n ."+a.box+".warning ."+a.icon+"::after {\n top: 15%;\n left: 50%;\n margin-left: -1px;\n width: 2px;\n height: 2px;\n border-radius: 50%;\n }\n ."+a.box+".info ."+a.icon+"::before,\n ."+a.box+".warning ."+a.icon+"::before {\n top: calc(15% + 4px);\n left: 50%;\n margin-left: -1px;\n width: 2px;\n height: 40%;\n }\n ."+a.box+".error ."+a.icon+"::after, \n ."+a.box+".error ."+a.icon+"::before {\n top: 20%;\n left: 50%;\n width: 2px;\n height: 60%;\n margin-left: -1px;\n border-radius: 1px;\n }\n ."+a.box+".error ."+a.icon+"::after {\n transform: rotate(-45deg);\n }\n ."+a.box+".error ."+a.icon+"::before {\n transform: rotate(45deg);\n }\n ."+a.box+".success ."+a.icon+"::after {\n box-sizing: content-box;\n background-color: transparent;\n border: 2px solid #fff;\n border-left: 0;\n border-top: 0;\n height: 50%;\n left: 35%;\n top: 13%;\n transform: rotate(45deg);\n width: 20%;\n transform-origin: center;\n }\n ").replace(/(\n|\t|\s)*/gi,"$1").replace(/\n|\t|\s(\{|\}|\,|\:|\;)/gi,"$1").replace(/(\{|\}|\,|\:|\;)\s/gi,"$1"),r.head.appendChild(s);var c=t.zIndex||1e4,f=[];return{show:e,info:function(n){e(n,"info")},success:function(n){e(n,"success")},warning:function(n){e(n,"warning")},error:function(n){e(n,"error")}}} $msg=useMessage(); // $msg.success('脚本开始运行') } },100) function injectCSS() { GM_addStyle(".error-log{list-style-type:none;padding:0}.error-log li{background-color:#f8d7da;color:#721c24;padding:10px;margin-top:5px;border:1px solid #f5c6cb;border-radius:5px;font-size:14px}"); GM_addStyle('.oj-button{z-index:10000;width:120px;height:30px;line-height:30px;text-align:center;color:black;font-weight:bold;cursor:pointer}.oj-panel{z-index:10000;background:white;position:fixed;left:20px;top:20px;width:390px;border:1px solid #000}.oj-header{position:relative;height:30px;background:rgb(250, 250, 250);cursor:move}.oj-close{position:absolute;right:10px;top:0;font-size:19px;cursor:pointer}.oj-body{min-height:200px;max-height:500px;overflow-y:auto;overflow-x:hidden}.oj-result{padding:10px;transition:.2s;font-size:14px;}.oj-result:hover{background:rgb(240,240,240);}') GM_addStyle('.ojh{}.ojh-button{}.ojh-save{background:rgb(246,246,246);}.ojh-save-content{display:inline-block;width:90%;height:200px;resize:none;margin:5px auto;vertical-align:top}.ojh-save-button{margin-left:100px}.ojh-body{max-height:500px;padding:10px;overflow-y:auto;background:rgb(240,240,240)}.ojh-file{position:relative;border-bottom:1px solid black;padding: 10px 2px;}.ojh-file-name{}.ojh-file-delete{display:block;position:absolute;right:10px;top:0}.ojh-file-content{display:block;width:100%;height:200px;resize:none;margin:5px auto}.text-button{display:inline-block;text-align:center;color:blue;cursor:pointer}'); } const status_map={"-2":{name:"Compile Error",short:"CE",color:"orange",type:"warning"},"-1":{name:"Wrong Answer",short:"WA",color:"#f8d7da",type:"error"},0:{name:"Accepted",short:"AC",color:"rgb(232,249,240)",type:"success"},1:{name:"Time Limit Exceeded",short:"TLE",color:"#f8d7da",type:"error"},2:{name:"Time Limit Exceeded",short:"TLE",color:"#f8d7da",type:"error"},3:{name:"Memory Limit Exceeded",short:"MLE",color:"#f8d7da",type:"error"},4:{name:"Runtime Error",short:"RE",color:"#f8d7da",type:"error"},5:{name:"System Error",short:"SE",color:"#f8d7da",type:"error"},6:{name:"Pending",color:"orange",type:"warning"},7:{name:"Judging",color:"blue",type:"info"},8:{name:"Partial Accepted",short:"PAC",color:"blue",type:"info"},9:{name:"Submitting",color:"yellow",type:"warning"}} const store = { headers: GM_getValue('oj-headers', []), pagesize: GM_getValue('oj-pagesize', 20), difficulty: GM_getValue('oj-difficulty', ""), problems: {}, hideAC: GM_getValue('oj-hideAC', false), } unsafeWindow.fuckingstore = store; function createElement(tag, clazz, attrs) { const elem = document.createElement(tag); elem.className = clazz; if (attrs) { for (let key in attrs) { elem[key] = attrs[key]; } } return elem; } function sel(selector) { return document.querySelector(selector); } function selall(selector) { return document.querySelector(selector); } let errorLog = null; function initErrorLog() { var cardBody = document.querySelectorAll('.ivu-card-body')[1]; if (!cardBody) { setTimeout(() => { initErrorLog(); }, 200); } else { errorLog = document.createElement('ul'); errorLog.id = 'errorLog'; errorLog.className = 'error-log'; cardBody.appendChild(errorLog); } } function addErrorLog(result, status, info) { if ([6, 7, 9].indexOf(result) != -1) { return; } var e = document.createElement('li'); let html = ""; html += status.short + "<br/>" if (result == -2) { // compile error html += info.err_info; } else { html += `耗时: ${info.time_cost} 内存:${submissionMemoryFormat(info.memory_cost)}` } e.innerHTML = html; e.style.backgroundColor = status.color; errorLog.prepend(e); } function submissionMemoryFormat(t) { if (void 0 === t) return "--"; var e = parseInt(t) / 1048576; return String(e.toFixed(0)) + "MB" } function initHeaderReplace() { if (!document.querySelector('.ivu-card-body')) { setTimeout(() => { initHeaderReplace(); }, 200); return; } const cardBody = document.querySelectorAll('.ivu-card-body')[1]; const ojh = createElement('div', 'ojh'); const body = createElement('div', 'ojh-body', {style: "display:none"}) cardBody.prepend(ojh); const showButton = createElement('a', 'ojh-button text-button', {textContent: "展示头文件", style: "margin-right:40px;"}); showButton.onclick = e => { if (showButton.textContent == '展示头文件') { showButton.textContent = '隐藏头文件'; body.style.display = 'block'; } else { showButton.textContent = '展示头文件'; body.style.display = 'none'; } } ojh.appendChild(showButton); const uploadButton = createElement('a', 'ojh-button text-button', {textContent: "添加头文件+", style: "margin-right:40px;"}); const save = createElement('div', 'ojh-save', {style: "display:none;"}); save.append(createElement('span', '', {innerText: "引用名称:"})) const saveInput = createElement('input', '', {style: "width:50%", placeholder: '代码中#include "../h/header.h",就填../h/header.h'}); save.append(saveInput); save.append(createElement('br')) save.append(createElement('span', '', {innerText: "文件内容:"})) const saveArea = createElement('textarea', 'ojh-save-content'); save.append(saveArea); const saveButton = createElement('button', 'ojh-save-button', {textContent: "保存"}); uploadButton.onclick = e => { if (uploadButton.textContent == '添加头文件+') { uploadButton.textContent = '添加头文件-'; save.style.display = 'block'; } else { uploadButton.textContent = '添加头文件+'; save.style.display = 'none'; } } saveButton.onclick = e => { console.log(saveInput.value, saveArea.value) if (!saveInput.value) { $msg.error("引用名称没填"); return; } if (!saveArea.value) { $msg.error("文件内容为空"); return; } saveHeader(body, saveInput.value, saveArea.value); saveInput.value = saveArea.value = "" } const replaceButton = createElement('button', 'ojh-button', {textContent: "替换内容"}); replaceButton.onclick = e => { $msg.info("开始替换代码中的头文件") let data = document.querySelector('.flex-container').__vue__.$data; let code = data.code; const regex = /#include "(.*?)"/g; while (true) { console.log("yici") let match; const headers = []; while ((match = regex.exec(code)) !== null) { headers.push(match[1]); } // 找是否存在header let find = false; for (let i = 0; i < headers.length; i++) { console.log("找是否匹配" + headers[i]); for (let storedHeader of store.headers) { if (headers[i] == storedHeader.name) { const escapedHeader = storedHeader.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const regex = new RegExp(`#include "${escapedHeader}"`, 'g'); let content = '// ' + storedHeader.name + "\n"; content += storedHeader.content + "\n"; code = code.replace(regex, content); find = true; break; } } } if (!find) { if (headers.length != 0) { $msg.info("未找到头文件:" + JSON.stringify(headers)) } break; } } data.code = code; $msg.success("替换完成") } save.appendChild(saveButton); ojh.appendChild(uploadButton); ojh.appendChild(replaceButton); ojh.appendChild(save); ojh.append(body); showHeaders(body) } function showHeader(body, header) { const file = createElement('div', 'ojh-file', {id: "ojh-file-" + header.name}); const name = createElement('span', 'ojh-file-name text-button', {innerText: header.name}); const wrapper = createElement('div', 'ojh-file-wrapper', {style: "display: none;"}) name.onclick = e => { if (wrapper.style.display == 'none') { wrapper.style.display = 'block'; } else { wrapper.style.display = 'none'; } } const deleteButton = createElement('a', 'ojh-file-delete text-button', {textContent: "删除"}) deleteButton.onclick = e => { if (confirm("是否删除?")) { deleteHeader(header.name) document.getElementById('ojh-file-' + header.name)?.remove(); } } file.append(name); file.append(deleteButton); const content = createElement('textarea', 'ojh-file-content', {value: header.content}) const save = createElement('button', '', {textContent: "保存"}); save.onclick = e => { saveHeader(body, header.name, content.value) } wrapper.append(content); wrapper.append(save); file.append(wrapper) body.append(file); } function showHeaders(body) { for (let header of store.headers) { showHeader(body, header); } } function saveHeader(body, name, content) { let headerElem = document.getElementById('ojh-file-' + name); if (!headerElem) { showHeader(body, {name, content}) } for (let header of store.headers) { // 找是否有重复的 if (header.name == name) { header.content = content; $msg.success("修改成功") GM_setValue('oj-headers', store.headers); return; } } store.headers.push({name, content}); GM_setValue('oj-headers', store.headers); $msg.success("添加成功") } function deleteHeader(name) { console.log("删除头文件" + name); for (let i = 0; i < store.headers.length; i++) { if (store.headers[i].name == name) { store.headers.splice(i, 1); break; } } GM_setValue('oj-headers', store.headers); $msg.success("删除成功") } function initAnswerPanel() { if (!document.querySelector('.oj-menu')) { setTimeout(() => { initAnswerPanel(); }, 200); return; } if (document.querySelector('.oj-panel')) { return; } const panel = createElement('div', 'oj-panel') const header = createElement('div', 'oj-header') const close = createElement('div', 'oj-close') close.innerText = '×' const body = createElement('div', 'oj-body') header.appendChild(close) panel.appendChild(header) panel.appendChild(body) document.body.appendChild(panel) let lastX = GM_getValue('box_last_x', 100) let lastY = GM_getValue('box_last_y', 100) panel.style.left = lastX + 'px' panel.style.top = lastY + 'px' panel.style.display = 'none' header.addEventListener('mousedown', makeDraggableFunction(panel, false, null, () => { GM_setValue('box_last_x', parseInt(panel.style.left)) GM_setValue('box_last_y', parseInt(panel.style.top)) }), false) const showButton = createElement('span', 'oj-button ivu-menu-item') showButton.innerText = '看可见答案' document.querySelector('.oj-menu').appendChild(showButton) showButton.addEventListener('click', e => { showButton.style.display = 'none' panel.style.display = 'block' body.innerHTML = '' getAnswers() }) close.addEventListener('click', e => { panel.style.display = 'none' showButton.style.display = 'block' stop = true; }) } function makeDraggableFunction(elem, allowMoveOut, exec, callback) { let handleMouseDown = function (event) { let offsetX = parseInt(elem.style.left) let offsetY = parseInt(elem.style.top) let innerX = event.clientX - offsetX let innerY = event.clientY - offsetY if (!!exec) { exec() } document.onmousemove = function (event) { elem.style.left = event.clientX - innerX + 'px' elem.style.top = event.clientY - innerY + 'px' if (!allowMoveOut) { if (parseInt(elem.style.left) <= 0) { elem.style.left = '0px' } if (parseInt(elem.style.top) <= 0) { elem.style.top = '0px' } if ( parseInt(elem.style.left) >= window.innerWidth - parseInt(elem.style.width) ) { elem.style.left = window.innerWidth - parseInt(elem.style.width) + 'px' } if ( parseInt(elem.style.top) >= window.innerHeight - parseInt(elem.style.height) ) { elem.style.top = window.innerHeight - parseInt(elem.style.height) + 'px' } } } document.onmouseup = function () { document.onmousemove = null document.onmouseup = null if (!!callback) { callback() } } } return handleMouseDown } let stop = false; function getProblemId() { let url = location.href; if (url.indexOf('/problem') != -1) { url = url.substring(url.lastIndexOf('/') + 1); return parseInt(url); } return null; } function getAnswers() { let problemId = getProblemId(); if (!problemId) { alert("未在答题界面!") return; } stop = false; let total = -1; getList(problemId, 1); } function getList(problemId, page) { const offset = (page - 1) * 100; fetch(`https://www.bjfuacm.com/api/submissions?myself=0&result=0&username=&page=${page}&problem_id=${problemId}&limit=100&offset=${offset}`, { "headers": { "accept-language": "zh-CN,zh;q=0.9", "content-type": "application/json;charset=utf-8", }, "body": null, "method": "GET", }).then(res => res.json()).then(res => { // console.log(res) const {results, total} = res.data; let html = '' for (let result of results) { if (result.show_link) { const info = result.statistic_info; html += `<div class="oj-result"> <a href="https://www.bjfuacm.com/status/${result.id}" target="_blank">查看答案</a> <span>耗时${info.time_cost}MS 内存${submissionMemoryFormat(info.memory_cost)} 作者<a href="https://www.bjfuacm.com/user-home?username=${result.username}" target="_blank">${result.username}</a></span></div>` } } document.querySelector('.oj-body').innerHTML += html; if (!stop && offset < total) { getList(problemId, page + 1); } else { document.querySelector('.oj-body').innerHTML += "<p style='text-align:center;'>—————— 已加载全部 ——————</p>"; } }).catch(err => { console.error(err); alert("ERROR: " + err) }); } /** * 为list添加每页展示数量的选项 */ function initPageOption(parent, onchange) { if (!sel('.content-app')) { setTimeout(initPageOption, 200); return; } setTimeout(() => { let vue = sel('.content-app').children[0].__vue__; console.log(vue) if (parent) { vue = vue.$parent; } vue.limit = store.pagesize; if (onchange) { onchange(vue, store.pagesize) } const filter = sel('.filter'); const pageSelect = createElement('select', '', {style: "margin-right:10px;"}) for (let pagesize of [10, 20, 30, 50, 100]) { const option = createElement('option', '', {label: `${pagesize}个/页`, textContent: `${pagesize}个/页`, value: pagesize}); pageSelect.append(option); if (vue.limit == pagesize) { pageSelect.value = pagesize; } } pageSelect.onchange = e => { if (onchange) { onchange(vue, pageSelect.value); } store.pagesize = pageSelect.value; GM_setValue('oj-pagesize', store.pagesize); } filter.prepend(pageSelect); if (parent) { // if in problem list page const hideACLabel = createElement('label', '', {style: 'margin-right:10px', innerText: '隐藏已完成'}); const hideACCheck = createElement('input', '', {type: 'checkbox', checked: store.hideAC}); hideACCheck.onchange = e => { store.hideAC = hideACCheck.checked; GM_setValue('oj-hideAC', store.hideAC); onchange(vue, pageSelect.value); } hideACLabel.append(hideACCheck); filter.prepend(hideACLabel); } }, 200) } function hookRequest() { const originalOpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function(method, url) { if (url.indexOf('/api/submission?id=') != -1) { this.addEventListener('readystatechange', function() { if (this.readyState == 4) { const res = JSON.parse(this.responseText); const result = res.data.result; const status = status_map[result] console.log(res, status) if (!res.data.statistic_info) { return; } const info = res.data.statistic_info; addErrorLog(result, status, info); store.problems[res.data.problem] = {status: result}; } }) } else if (url.indexOf('/api/profile') != -1) { this.addEventListener('readystatechange', function() { if (this.readyState == 4) { const res = JSON.parse(this.responseText); store.problems = res.data.acm_problems_status.problems; console.log('SOLVED: ', store.problems) } }) } else if (url.indexOf('/api/problem') != -1) { this.addEventListener('readystatechange', function() { if (this.readyState == 4) { const res = JSON.parse(this.responseText); const list = res.data.results; for (let i = 0; i < list.length; i++) { const problem = list[i]; let status = store.problems[problem.id] if (status) { if (status.status == 0) { if (store.hideAC) { list.splice(i, 1); i--; } else { problem.title = '✔️ ' + problem.title; } } else { problem.title = '❌ ' + problem.title; } } } Object.defineProperty(this, 'responseText', {writable: true}) this.responseText = JSON.stringify(res); } }) } originalOpen.apply(this, arguments); } } function urlContains(url, targets) { for (let target of targets) { if (url.indexOf(target) != -1) { return true; } } return false; } let lastPath = "DAMNSONWHEREDYOUFINDTHAT"; setInterval(() => { if (location.pathname != lastPath) { lastPath = location.pathname; if (lastPath.indexOf('/problem/') != -1) { injectCSS(); initErrorLog(); initAnswerPanel(); initHeaderReplace(); } else if (['/problems', '/structure', '/started'].indexOf(lastPath) != -1) { initPageOption(true, (vue, pagesize) => { vue.limit = pagesize; vue.query.page = 1; if (!vue.query.difficulty || vue.query.difficulty == '') { vue.query.difficulty = store.difficulty; } else { store.difficulty = vue.query.difficulty; GM_setValue('oj-difficulty', store.difficulty); } vue.getProblemList(); }); } else if (lastPath == '/status') { initPageOption(false, (vue, pagesize) => { vue.limit = pagesize; vue.page = 1; vue.getSubmissions(); }); } console.log("path切换为" + lastPath) } }, 500); hookRequest(); })()
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址