// ==UserScript==
// @name 梦熊OJ将题目标题颜色改为洛谷原题难度代表色
// @namespace http://tampermonkey.net/
// @version 1.0
// @description new!
// @author DeepSeek
// @license MIT
// @match https://www.mxoj.net/problem/*
// @match https://www.mxoj.net/training/problems*
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @connect www.luogu.com.cn
// @run-at document-idle
// ==/UserScript==
(function() {
'use strict';
// 配置参数
const CONFIG = {
debug: true,
maxRetry: 3,
retryDelay: 500,
colorTransition: 'color 0.5s ease',
// 题目页特定选择器
problemTitleSelector: '.info-title > h1.name',
// 题单页特定选择器 - 使用更可靠的选择器
problemListContainer: '.problem-list',
problemListTitleSelector: 'a[href*="/problem/"]'
};
// 洛谷难度颜色映射
const DIFFICULTY_COLORS = {
'入门': '#FE4C61',
'普及-': '#F39C11',
'普及/提高-': '#FFC116',
'普及+/提高': '#52C41A',
'提高+/省选-': '#3498DB',
'省选/NOI-': '#9D3DCF',
'NOI/NOI+/CTSC': '#0E1D69',
'暂无评定': '#BFBFBF'
};
// 添加关键样式
GM_addStyle(`
/* 强制颜色覆盖 */
.luogu-colored-title {
color: var(--luogu-difficulty-color) !important;
transition: ${CONFIG.colorTransition} !important;
}
/* 最强覆盖方案 - 使用动画强制应用样式 */
.luogu-force-color-override {
animation: luogu-color-blink 0.1s !important;
}
@keyframes luogu-color-blink {
0% { opacity: 0.99; }
100% { opacity: 1; }
}
`);
// 主控制器
class DifficultyColorizer {
constructor() {
this.difficultyCache = new Map();
this.observer = null;
this.init();
}
init() {
if (this.isProblemPage()) {
this.setupProblemPage();
} else if (this.isProblemSetPage()) {
this.setupProblemSetPage();
}
}
// 页面类型判断
isProblemPage() {
return window.location.pathname.includes('/problem/') &&
!window.location.pathname.includes('/problems');
}
isProblemSetPage() {
return window.location.pathname.includes('/training/problems');
}
// 题目页面设置 - 使用精确选择器
setupProblemPage() {
// 使用精确的选择器避免选中其他元素
this.waitForElement(CONFIG.problemTitleSelector, (titleElement) => {
const problemId = this.extractProblemIdFromURL();
const luoguId = this.convertToLuoguId(problemId);
if (luoguId) {
this.forceApplyColor(titleElement, luoguId);
} else if (CONFIG.debug) {
console.log('无法转换题号:', problemId);
}
});
}
// 题单页面设置 - 增强选择器逻辑
setupProblemSetPage() {
// 初始处理
this.processProblemTitles();
// 设置观察器
this.setupProblemSetObserver();
}
// 设置题单页面观察器
setupProblemSetObserver() {
// 移除旧观察器
if (this.observer) {
this.observer.disconnect();
}
// 创建新观察器
this.observer = new MutationObserver((mutations) => {
this.processProblemTitles();
});
// 观察整个文档变化
this.observer.observe(document.body, {
childList: true,
subtree: true
});
}
// 处理题单页题目 - 使用更可靠的选择器方法
processProblemTitles() {
try {
// 方法1: 使用容器内的选择器
const container = document.querySelector(CONFIG.problemListContainer);
let titles = [];
if (container) {
// 在容器内查找题目链接
titles = container.querySelectorAll(CONFIG.problemListTitleSelector);
} else {
// 方法2: 回退到全局查找
titles = document.querySelectorAll(CONFIG.problemListTitleSelector);
}
if (CONFIG.debug) {
console.log(`找到 ${titles.length} 个题目标题`);
}
titles.forEach((title) => {
if (title.dataset.luoguColored) return;
title.dataset.luoguColored = 'true';
const problemId = this.extractProblemIdFromElement(title);
if (CONFIG.debug) {
console.log('处理题目:', {
text: title.textContent,
href: title.href,
problemId: problemId
});
}
const luoguId = this.convertToLuoguId(problemId);
if (luoguId) {
if (CONFIG.debug) {
console.log(`转换题号: ${problemId} → ${luoguId}`);
}
// 立即添加标记类
title.classList.add('luogu-colored-title', 'luogu-force-color-override');
// 应用颜色
this.applyColorToTitle(title, luoguId);
}
});
} catch (error) {
console.error('处理题单标题出错:', error);
}
}
// 应用颜色到标题
async applyColorToTitle(titleElement, luoguId) {
try {
// 优先使用缓存
if (this.difficultyCache.has(luoguId)) {
const difficultyData = this.difficultyCache.get(luoguId);
this.applyColor(titleElement, difficultyData);
return;
}
// 获取难度数据
const difficultyData = await this.fetchDifficulty(luoguId);
if (difficultyData) {
this.difficultyCache.set(luoguId, difficultyData);
this.applyColor(titleElement, difficultyData);
}
} catch (error) {
console.error('应用颜色出错:', error);
}
}
// 应用颜色
applyColor(element, difficultyData) {
if (!element || !difficultyData) return;
const color = DIFFICULTY_COLORS[difficultyData.level] || difficultyData.color;
// 应用CSS变量
element.style.setProperty('--luogu-difficulty-color', color, 'important');
// 直接设置颜色属性
element.style.color = color;
// 添加难度提示
element.title = `洛谷难度: ${difficultyData.level}`;
if (CONFIG.debug) {
console.log('应用颜色成功:', {
element: element,
color: color,
level: difficultyData.level
});
}
}
// 强力应用颜色 - 题目页专用
async forceApplyColor(element, luoguId, attempt = 0) {
if (!element || attempt > CONFIG.maxRetry) {
if (CONFIG.debug) console.log('停止重试:', luoguId);
return;
}
try {
// 获取难度数据
let difficultyData;
if (this.difficultyCache.has(luoguId)) {
difficultyData = this.difficultyCache.get(luoguId);
} else {
difficultyData = await this.fetchDifficulty(luoguId);
if (difficultyData) {
this.difficultyCache.set(luoguId, difficultyData);
}
}
if (difficultyData) {
this.applyColor(element, difficultyData);
} else {
setTimeout(() => {
this.forceApplyColor(element, luoguId, attempt + 1);
}, CONFIG.retryDelay);
}
} catch (error) {
console.error('应用颜色出错:', error);
}
}
// 从URL提取题目ID
extractProblemIdFromURL() {
const urlMatch = window.location.pathname.match(/\/problem\/([A-Za-z]+[\dA-Za-z]*)/i);
return urlMatch ? urlMatch[1] : null;
}
// 从元素提取题目ID
extractProblemIdFromElement(element) {
if (element.href) {
const match = element.href.match(/\/problem\/([A-Za-z]+[\dA-Za-z]*)/i);
if (match) return match[1];
}
// 备用方法:从文本内容提取
const text = element.textContent.trim();
const textMatch = text.match(/\[?([A-Za-z]+[\dA-Za-z]*)\]?/);
return textMatch ? textMatch[1] : null;
}
// 转换为洛谷题目ID
convertToLuoguId(problemId) {
if (!problemId) return null;
// LB题号处理: LB123 → B123
if (problemId.startsWith('LB') && problemId.length > 2) {
return 'B' + problemId.substring(2);
}
// L题号处理: L123 → P123
if (problemId.startsWith('L') && problemId.length > 1) {
return 'P' + problemId.substring(1);
}
// CF题号保持不变
if (problemId.startsWith('CF')) {
return problemId;
}
return null;
}
// 获取难度数据 - 优化解析逻辑
fetchDifficulty(luoguId) {
return new Promise((resolve) => {
GM_xmlhttpRequest({
method: "GET",
url: `https://www.luogu.com.cn/problem/${luoguId}`,
onload: (response) => {
try {
// 方法1: 从题目标签中提取(最快)
const tagMatch = response.responseText.match(/"difficulty":(\d+)/);
if (tagMatch) {
const levelMap = {
0: '暂无评定',
1: '入门',
2: '普及-',
3: '普及/提高-',
4: '普及+/提高',
5: '提高+/省选-',
6: '省选/NOI-',
7: 'NOI/NOI+/CTSC'
};
const level = levelMap[tagMatch[1]] || '暂无评定';
resolve({ level, color: DIFFICULTY_COLORS[level] });
return;
}
// 方法2: 从JSON数据提取
const jsonMatch = response.responseText.match(/JSON\.parse\('(.+?)'\)/);
if (jsonMatch) {
try {
const jsonStr = jsonMatch[1].replace(/\\"/g, '"');
const data = JSON.parse(jsonStr);
if (data.currentData?.problem?.difficulty) {
const level = data.currentData.problem.difficulty;
resolve({ level, color: DIFFICULTY_COLORS[level] });
return;
}
} catch (e) {
// JSON解析失败,继续尝试其他方法
}
}
// 方法3: 从HTML提取
const htmlMatch = response.responseText.match(/<span style="color:([^"]+)"[^>]*>([^<]+)<\/span>/);
if (htmlMatch) {
resolve({ level: htmlMatch[2], color: htmlMatch[1] });
return;
}
resolve(null);
} catch (error) {
console.error('解析难度出错:', error);
resolve(null);
}
},
onerror: () => {
if (CONFIG.debug) console.log('网络请求失败:', luoguId);
resolve(null);
},
// 添加超时处理
timeout: 5000
});
});
}
// 等待元素出现 - 使用精确选择器
waitForElement(selector, callback, attempt = 0) {
const element = document.querySelector(selector);
if (element) {
callback(element);
} else if (attempt < 5) {
setTimeout(() => this.waitForElement(selector, callback, attempt + 1), 200);
} else if (CONFIG.debug) {
console.log('未找到元素:', selector);
}
}
}
// 启动插件
const colorizer = new DifficultyColorizer();
// 调试信息
if (CONFIG.debug) {
console.log('洛谷题目难度颜色插件已启动');
console.log('当前页面:', window.location.href);
console.log('配置:', CONFIG);
console.log('题单容器选择器:', CONFIG.problemListContainer);
console.log('题目标题选择器:', CONFIG.problemListTitleSelector);
}
})();