简易洛谷插件new

· · 科技·工程

简易洛谷插件new

0.版本及更新日志

::::info[更新日志] :::info[2025/12/7]

::: :::: 当前版本:**1.9** ## 1.说明 在斯老板[简易洛谷插件](https://www.luogu.com.cn/article/dxg99wmz)的基础上进行优化。 本代码在写作完成后使用 DeepSeek 进行了润色。 ## 2.更新要点 * 新前端可在侧边栏“更多功能”里的最后一项找到; * 不再用临时网页,而使用弹窗; * 保存成功自动刷新。 其余请见更新日志。 ## 3.代码 ```javascript // ==UserScript== // @name luogu_bot1 // @namespace http://tampermonkey.net/ // @version 1.9 // @description 自制洛谷插件 // @author Hel_Y // @match https://www.luogu.com.cn/* // @grant none // @run-at document-end // ==/UserScript== 'use strict'; (function(){ const featureDict = new Map([ ['lgbot1RemoveAd', '屏蔽广告'], ['lgbot1Removeback', '移除网页出错跳转'], ['lgbot1AddMessageLink', '私信界面 Ctrl+Click 打开用户主页'], ['lgbot1RemoveCover', '移除主页遮盖'], ['lgbot1AddProblemsColor', '显示题目颜色'] ]); const problemDifficultyToColor = [[191,191,191],[254,76,97],[243,156,17],[255,193,22],[82,196,26],[52,152,219],[157,61,207],[14,29,105]]; // 添加弹窗样式 function addModalStyles() { const style = document.createElement('style'); style.textContent = ` .lgbot1-modal.hide { z-index: -114514; opacity: 0; pointer-events: none; transition: opacity .3s ease, z-index 0s ease .3s; } .lgbot1-modal { top: 0; left: 0; right: 0; bottom: 0; z-index: 10000; overflow: auto; position: fixed; transition: opacity .3s ease; display: flex; justify-content: center; align-items: center; } .lgbot1-modal > .background { top: 0; left: 0; right: 0; bottom: 0; position: absolute; z-index: -1; background-color: rgba(0, 0, 0, 0.5); } .lgbot1-modal .l-card { position: relative; z-index: 1; background: white; border-radius: 8px; box-shadow: 0 4px 16px rgba(0,0,0,0.2); padding: 24px; min-width: 560px; max-width: 600px; margin: 20px; animation: modalSlideIn 0.3s ease-out; } @keyframes modalSlideIn { from { opacity: 0; transform: translateY(-20px); } to { opacity: 1; transform: translateY(0); } } .lgbot1-modal .close { position: absolute; right: 16px; top: 16px; cursor: pointer; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; color: #999; transition: color 0.2s; } .lgbot1-modal .close:hover { color: #333; } .lgbot1-modal .lfe-h3 { margin: 0 0 24px 0; font-size: 20px; font-weight: 600; color: #333; padding-right: 30px; } .lgbot1-modal .l-form-layout { margin-bottom: 20px; } .lgbot1-modal .l-form-layout.row { display: flex; align-items: flex-start; } .lgbot1-modal .l-form-layout.row > span { width: 120px; padding-right: 16px; font-weight: 500; margin-top: 8px; color: #555; font-size: 14px; } .lgbot1-modal .l-form-layout.row > div { flex: 1; } .lgbot1-modal #featureSettings label { display: flex; align-items: center; cursor: pointer; margin-bottom: 12px; padding: 8px 12px; border-radius: 6px; transition: background-color 0.2s; } .lgbot1-modal #featureSettings label:hover { background-color: #f5f5f5; } .lgbot1-modal #featureSettings input[type="checkbox"] { margin-right: 12px; width: 16px; height: 16px; cursor: pointer; } .lgbot1-modal .button-group { display: flex; gap: 12px; justify-content: flex-end; margin-top: 24px; } .lgbot1-modal button.lform-size-middle { padding: 10px 24px; border: 1px solid #d9d9d9; background: #fafafa; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 500; color: #333; transition: all 0.2s; min-width: 80px; } .lgbot1-modal button.lform-size-middle:hover { background: #eaeaea; border-color: #bfbfbf; } .lgbot1-modal button.lform-size-middle#saveFeatures { background: #1890ff; border-color: #1890ff; color: white; } .lgbot1-modal button.lform-size-middle#saveFeatures:hover { background: #40a9ff; border-color: #40a9ff; } /* 添加保存成功弹窗样式 */ .lgbot1-save-toast { position: fixed; top: 20px; right: 20px; z-index: 10001; background: #f6ffed; border: 1px solid #b7eb8f; border-radius: 4px; padding: 12px 16px; display: flex; align-items: center; gap: 12px; animation: toastSlideIn 0.3s ease-out; box-shadow: 0 4px 12px rgba(0,0,0,0.15); } .lgbot1-save-toast-content { display: flex; align-items: center; gap: 8px; } .lgbot1-save-toast-icon { color: #52c41a; width: 20px; height: 20px; flex-shrink: 0; } .lgbot1-save-toast-text { color: #52c41a; font-size: 14px; font-weight: 500; } @keyframes toastSlideIn { from { opacity: 0; transform: translateX(100%); } to { opacity: 1; transform: translateX(0); } } @keyframes toastSlideOut { from { opacity: 1; transform: translateX(0); } to { opacity: 0; transform: translateX(100%); } } `; document.head.appendChild(style); } // 显示保存成功提示并刷新 function showSaveToastAndRefresh() { window.fdsa=document.createElement("div"); window.fdsa.className="swal2-container swal2-top-end"; window.fdsa.style="height:92px;width:360px;margin-left:auto;transition:all .25s linear;opacity:1;"; window.fdsa.innerHTML='<div aria-labelledby="swal2-title" aria-describedby="swal2-html-container" class="swal2-popup swal2-toast swal2-icon-success swal2-show" tabindex="-1" role="alert" aria-live="polite" style="width:360px;display: grid;"><div class="swal2-icon swal2-success swal2-icon-show" style="display:flex;"><div class="swal2-success-circular-line-left" style="background-color: rgb(255, 255, 255);"></div><span class="swal2-success-line-tip"></span> <span class="swal2-success-line-long"></span><div class="swal2-success-ring"></div><div class="swal2-success-fix" style="background-color: rgb(255, 255, 255);"></div><div class="swal2-success-circular-line-right" style="background-color: rgb(255, 255, 255);"></div></div><h2 class="swal2-title" id="swal2-title" style="display: block;">保存成功</h2></div>'; // 添加到页面 document.body.appendChild(window.fdsa); setTimeout(window.closem,3000); setTimeout(window.closem2,3250); location.reload(); } // 初始化设置 function initializeSettings(){ for(let i of featureDict.keys()){ if(localStorage.getItem(i) === null){ localStorage.setItem(i, 'true'); } } } // 创建弹窗 function createModal() { // 检查是否已存在弹窗 if (document.querySelector('.lgbot1-modal')) { return; } // 添加样式 addModalStyles(); // 创建弹窗 const modal = document.createElement('div'); modal.className = 'lgbot1-modal hide'; // 弹窗内容 modal.innerHTML = ` <div class="background"></div> <div class="l-card"> <div style="position: relative;"> <h3 class="lfe-h3">选择插件功能</h3> <div class="close"> <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M12.72 3.28a.75.75 0 10-1.06-1.06L8 6.94 4.34 3.28a.75.75 0 10-1.06 1.06L6.94 8l-3.66 3.66a.75.75 0 101.06 1.06L8 9.06l3.66 3.66a.75.75 0 101.06-1.06L9.06 8l3.66-3.66z"/> </svg> </div> </div> <div> <form class="modal"> <div class="l-form-layout row"> <div> <div id="featureSettings"></div> </div> </div> <div class="button-group"> <button class="lform-size-middle" type="button" id="selectAll">全选</button> <button class="lform-size-middle" type="button" id="saveFeatures">保存</button> </div> </form> </div> </div> `; // 添加弹窗到页面 document.body.appendChild(modal); // 绑定事件 bindModalEvents(); } // 绑定弹窗事件 function bindModalEvents() { const modal = document.querySelector('.lgbot1-modal'); if (!modal) return; const closeBtn = modal.querySelector('.close'); const background = modal.querySelector('.background'); const selectAllBtn = modal.querySelector('#selectAll'); const saveBtn = modal.querySelector('#saveFeatures'); // 关闭弹窗函数 function closeModal() { modal.classList.add('hide'); } // 绑定关闭事件 if (closeBtn) closeBtn.onclick = closeModal; if (background) background.onclick = closeModal; // 绑定全选按钮 if (selectAllBtn) { selectAllBtn.onclick = function() { const checkboxes = modal.querySelectorAll('#featureSettings input[type="checkbox"]'); checkboxes.forEach(cb => cb.checked = true); }; } // 绑定保存按钮 if (saveBtn) { saveBtn.onclick = function() { // 保存设置 const checkboxes = modal.querySelectorAll('#featureSettings input[type="checkbox"]'); checkboxes.forEach(cb => { localStorage.setItem(cb.id, String(cb.checked)); }); // 关闭弹窗 closeModal(); // 显示保存成功提示并刷新 showSaveToastAndRefresh(); }; } } // 生成功能选项 function renderFeatureSettings() { const modal = document.querySelector('.lgbot1-modal'); if (!modal) return; const featureSettings = modal.querySelector('#featureSettings'); if (!featureSettings) return; featureSettings.innerHTML = ''; for(let [key, description] of featureDict){ const div = document.createElement('div'); const label = document.createElement('label'); const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.id = key; checkbox.checked = localStorage.getItem(key) === 'true'; label.appendChild(checkbox); label.appendChild(document.createTextNode(description)); div.appendChild(label); featureSettings.appendChild(div); } } // 显示弹窗 function showModal() { const modal = document.querySelector('.lgbot1-modal'); if (!modal) return; modal.classList.remove('hide'); renderFeatureSettings(); } function addSettingButton(){//增加插件设置(原有左侧导航栏) const navElement = document.querySelector('nav.lfe-body'); if(!navElement){ console.log("未找到侧边导航栏"); console.log(document); return; } // 创建按钮外壳 <a> const settingElement = document.createElement('a'); settingElement.setAttribute('data-v-0640126c', ''); settingElement.setAttribute('data-v-639bc19b', ''); settingElement.setAttribute('data-v-33633d7e', ''); settingElement.className = 'color-none'; settingElement.style.color = 'inherit'; settingElement.href = '#'; // 设置为#以防止页面跳转 // 1. 创建图标容器 <span class="icon"> const iconSpan = document.createElement('span'); iconSpan.setAttribute('data-v-639bc19b', ''); iconSpan.setAttribute('data-v-0640126c', ''); iconSpan.className = 'icon'; // 创建SVG齿轮图标容器 const gearSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); gearSvg.setAttribute('data-v-639bc19b', ''); gearSvg.setAttribute('data-v-0640126c', ''); gearSvg.setAttribute('aria-hidden', 'true'); gearSvg.setAttribute('focusable', 'false'); // 关键:设置足够大的 viewBox gearSvg.setAttribute('viewBox', '0 0 16 16'); // 保持原始 viewBox gearSvg.setAttribute('class', 'svg-inline--fa'); // 关键:通过 width/height 控制显示大小,匹配其他图标 gearSvg.style.width = '1.3em'; // 从 1.2em 增加到 1.25em gearSvg.style.height = '1.3em'; // 垂直对齐也可微调,如果感觉图标偏高或偏低 gearSvg.style.verticalAlign = '-0.2em'; // 创建并设置路径 (你提供的原始路径,未修改) const path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); path.setAttribute('fill', 'currentColor'); // 直接使用你提供的原始d属性 path.setAttribute('d', 'M14 8.77v-1.6l-1.94-.64-.45-1.09.88-1.84-1.13-1.13-1.81.91-1.09-.45-.69-1.92h-1.6l-.63 1.94-1.11.45-1.84-.88-1.13 1.13.91 1.81-.45 1.09L0 7.23v1.59l1.94.64.45 1.09-.88 1.84 1.13 1.13 1.81-.91 1.09.45.69 1.92h1.59l.63-1.94 1.11-.45 1.84.88 1.13-1.13-.92-1.81.47-1.09L14 8.75v.02zM7 11c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3z'); gearSvg.appendChild(path); iconSpan.appendChild(gearSvg); // 2. 创建文字容器 <span class="text"> const textSpan = document.createElement('span'); textSpan.setAttribute('data-v-639bc19b', ''); textSpan.setAttribute('data-v-0640126c', ''); textSpan.className = 'text'; textSpan.textContent = '插件设置'; textSpan.style.marginLeft = '4px'; // 添加与图标的小间距 // 将图标和文字添加到按钮中 settingElement.appendChild(iconSpan); settingElement.appendChild(textSpan); // 注意:这里有一个空格文本节点在参考代码中,为简化可省略 // 将按钮添加到导航栏 navElement.appendChild(settingElement); // 点击设置按钮显示弹窗 settingElement.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); showModal(); }); } // 在侧边栏添加插件设置按钮 function addSidebarPluginButton() { // 查找侧边栏区域,模仿参考代码中的选择器 const sidebar = document.querySelector(".nav-group.on-expand ul"); if (!sidebar) { console.log("未找到侧边栏区域,将在页面加载完成后重试"); // 等待页面完全加载 setTimeout(addSidebarPluginButton, 1000); return; } // 检查是否已经添加过按钮 if (document.querySelector("#luoguBot1SettingsBtn")) { return; } // 创建按钮HTML,模仿参考代码中的结构 const buttonHTML = ` <li data-v-40281d0d="" data-v-6c9e83f4="" title="插件设置"> <a data-v-12b24cc3="" data-v-40281d0d="" href="#" class="" disabled="false" id="luoguBot1SettingsBtn"> <span data-v-40281d0d="" class="title minor">插件设置</span> </a> </li> `; // 插入按钮到侧边栏末尾 sidebar.insertAdjacentHTML("beforeend", buttonHTML); // 绑定点击事件 document.getElementById("luoguBot1SettingsBtn").addEventListener("click", function(event) { event.preventDefault(); showModal(); }); console.log("已在侧边栏添加插件设置按钮"); } function removeAd(){//屏蔽广告 const adElement = document.querySelector('div[data-v-0a593618][data-v-1143714b]'); if(adElement) { adElement.remove(); console.log('广告已被删除'); } else console.log('没有找到广告'); } function removeBack(){//移除网页跳转 function disableRedirect(){ window.history.go = function() {// 根据实际测试,洛谷使用的是 history.go console.log("luogu_bot1:已为您屏蔽 history.go()"); }; } function checkError(){ if(document.title === '错误 - 洛谷 | 计算机科学教育新生态'){ disableRedirect(); return true; } return false; } const observer = new MutationObserver((mutations, obs) => { checkError(); }); const config = { childList: true, subtree: true, }; observer.observe(document, config);// 监视 title 变化 if(document.readyState === 'complete' || document.readyState === 'interactive'){ checkError(); } } async function getUidByUsername(username){// 获取 uid const apiUrl = `https://www.luogu.com.cn/api/user/search?keyword=${username}`; return await fetch(apiUrl) .then(response => response.json()) .then(data => { if(data.users && data.users.length > 0){ return data.users[0].uid; } }) .catch(error => { console.error('Error fetching user data:', error); }); } async function processItem(item) { // 添加 eventListener item.parentElement.parentElement.addEventListener('click', (event) => { if(event.ctrlKey){ getUidByUsername(item.textContent.trim()) .then(uid => { const userLink = `/user/${uid}`; if(userLink){ window.open(userLink, '_blank'); } }) } }); } function addMessageLink(){ // Ctrl+Click 触发,动态修改网页 let items = document.querySelectorAll('span[data-v-5b9e5f50] > span[slot="trigger"]'); for(let i of items){ processItem(i); } const callback = async function(mutationsList, observer) { for (const mutation of mutationsList) { if (mutation.type === 'childList') { for (const addedNode of mutation.addedNodes) { if (addedNode.nodeType === Node.ELEMENT_NODE) { const newItems = addedNode.querySelectorAll('span[data-v-5b9e5f50] > span[slot="trigger"]'); for (const newItem of newItems) { processItem(newItem); } } } } } }; const observer = new MutationObserver(callback); observer.observe(document, { childList: true, subtree: true }); } function removeCover(){ let profile = document.querySelector(".introduction.marked"); if(profile && profile.style.display === "none"){ profile.style.display = "block"; for(let i=0;i<profile.parentElement.children.length;++i){ if(profile.parentElement.children[i].innerText === "系统维护,该内容暂不可见。"){ profile.parentElement.children[i].remove(); } } } } function alwaysRemoveCover(){ const observer = new MutationObserver(() => removeCover()); observer.observe(document,{ childList: true, subtree: true }); removeCover(); } const problemToColorMap = new Map(); class FetchRateLimiter{ constructor(limit) { // limit 以毫秒为单位,表示相邻两次 fetch 操作间的最小间隔 this.limit = limit; this.queue = []; this.queuePrior = []; this.active = false; this.requestCache = new Map(); } process() { this.active = 1; let resolve, reject, url; if(this.queuePrior.length > 0){ ({resolve, reject, url} = this.queuePrior.shift()); } else if(this.queue.length > 0){ ({resolve, reject, url} = this.queue.shift()); } else{ this.active = 0; return; } fetch(url) .then(resolve) .catch(reject); setTimeout(this.process.bind(this), this.limit); } push(url, prior) { if(this.requestCache.has(url)) return this.requestCache.get(url); const request = new Promise((resolve, reject) => { if(prior) this.queuePrior.push({url, resolve, reject}); else this.queue.push({url, resolve, reject}); if(!this.active) this.process(); }) .then((response) => { return response.text(); }); this.requestCache.set(url,request); return request; } } const limiter = new FetchRateLimiter(300); async function getProblemColor(problemid, prior=false){ if(window.location.href.startsWith('https://www.luogu.com.cn/record/list')){ const resultList = _feInstance.currentData.records.result; if(!resultList.lgbot1Visited){ for(const item of resultList){ problemToColorMap.set(item.problem.pid,`rgb(${problemDifficultyToColor[item.problem.difficulty].join(',')})`); } resultList.lgbot1Visited = true; } } if(/^https:\/\/www\.luogu\.com\.cn\/user\/\d+#practice$/.test(window.location.href)) { let problemList = _feInstance.currentData.submittedProblems; if(!problemList.lgbot1Visited){ for(const item of problemList){ problemToColorMap.set(item.pid,`rgb(${problemDifficultyToColor[item.difficulty].join(',')})`); } problemList.lgbot1Visited = true; } problemList = _feInstance.currentData.passedProblems; if(!problemList.lgbot1Visited){ for(const item of problemList){ problemToColorMap.set(item.pid,`rgb(${problemDifficultyToColor[item.difficulty].join(',')})`); } problemList.lgbot1Visited = true; } } if(problemToColorMap.has(problemid)) return problemToColorMap.get(problemid); const url = '/problem/'+problemid; let data; try{ data = await limiter.push(url, prior); } catch (error) { console.error('Error fetching user data:', error); console.log(problemid); return; } const parser = new DOMParser(); const doc = parser.parseFromString(data, 'text/html'); let scriptText = ''; for(const i of doc.querySelectorAll("script")){ scriptText += i.textContent; } scriptText = scriptText.match(/"difficulty":\d/)[0]; debugger; if(!scriptText) return; const problemDifficulty = Number(scriptText[scriptText.length-1]); problemToColorMap.set(problemid,`rgb(${problemDifficultyToColor[problemDifficulty].join(',')})`); return problemToColorMap.get(problemid); } function isProblemId(problemid){ if(problemid.startsWith('AT_')) return true; if(!/[a-zA-Z]/.test(problemid)) return false; if(!/[0-9]/.test(problemid)) return false; return true; } async function addProblemColor(item){ let problemid = item.href.split('/').pop(); let prior = false; if(problemid.includes('?forum=')){ problemid = problemid.split('=').pop(); prior = true; } problemid = problemid.split('?')[0]; problemid = problemid.split('=').pop(); if(item.matches('a[data-v-bade3303][data-v-4842157a]')) if(problemid === "javascript:void 0") problemid = item.innerText.split(' ')[0]; if(!isProblemId(problemid)) return; if(item.innerText.startsWith(problemid)){ const spanItem = item.children[0]; if(spanItem && spanItem.matches('span.pid') && spanItem.innerText === problemid){ const color = await getProblemColor(problemid, prior); spanItem.style.color = color; spanItem.style.fontWeight = 'bold'; } else{ const color = await getProblemColor(problemid, prior); const content = item.innerHTML; item.innerHTML = content.replace(problemid,`<b style="color: ${color};">${problemid}</b>`); } } } async function addProblemsColor(){ const observer = new MutationObserver(async (mutationsList) => { for (const mutation of mutationsList) { if (mutation.type === 'childList') { for (const addedNode of mutation.addedNodes) { if (addedNode.nodeType === Node.ELEMENT_NODE) { const newItems = addedNode.querySelectorAll('a[href]'); if(addedNode.matches('a[href]')) addProblemColor(addedNode); for (const newItem of newItems) { addProblemColor(newItem); } } } } else if(mutation.type === 'characterData') { if(mutation.target.parentElement.matches('span.pid')){ mutation.target.parentElement.style.color = await getProblemColor(mutation.target.textContent); mutation.target.parentElement.style.fontWeight = 'bold'; } } } }); observer.observe(document, { childList: true, subtree: true, characterData: true, }); const nodelist = document.querySelectorAll('a[href]'); for(const i of nodelist){ addProblemColor(i); } } // 初始化 initializeSettings(); createModal(); // 页面加载后初始化 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', function() { setTimeout(function() { // 添加原有的左侧导航栏插件设置按钮 addSettingButton(); // 添加新的侧边栏插件设置按钮 addSidebarPluginButton(); // 启用功能 if(localStorage.getItem('lgbot1RemoveAd') === 'true'){ removeAd(); } if(localStorage.getItem('lgbot1Removeback') === 'true'){ removeBack(); } if(localStorage.getItem('lgbot1AddMessageLink') === 'true'){ if(window.location.href.startsWith('https://www.luogu.com.cn/chat')){ addMessageLink(); } } if(localStorage.getItem('lgbot1RemoveCover') === 'true'){ alwaysRemoveCover(); } if(localStorage.getItem('lgbot1AddProblemsColor') === 'true'){ addProblemsColor(); } }, 1000); }); } else { // DOMContentLoaded 已经触发 setTimeout(function() { // 添加原有的左侧导航栏插件设置按钮 addSettingButton(); // 添加新的侧边栏插件设置按钮 addSidebarPluginButton(); // 启用功能 if(localStorage.getItem('lgbot1RemoveAd') === 'true'){ removeAd(); } if(localStorage.getItem('lgbot1Removeback') === 'true'){ removeBack(); } if(localStorage.getItem('lgbot1AddMessageLink') === 'true'){ if(window.location.href.startsWith('https://www.luogu.com.cn/chat')){ addMessageLink(); } } if(localStorage.getItem('lgbot1RemoveCover') === 'true'){ alwaysRemoveCover(); } if(localStorage.getItem('lgbot1AddProblemsColor') === 'true'){ addProblemsColor(); } }, 1000); } })(); ``` 如有疑问可在评论区发出,本人会尽快优化。 :::align{center} ![](https://cdn.luogu.com.cn/upload/usericon/1447705.png?x-oss-process=image/circle,r_100/format,png) **Hel_Y** 爱好OI :::