油猴插件
用 AI 写的神秘玩意。
QOJ系列站点-导航右侧互跳
// ==UserScript==
// @name QOJ系列站点-导航右侧互跳
// @namespace https://github.com/
// @version 2.0
// @description 在qoj.ac/relia.uk/huang.lt/jiang.ly原生导航栏右侧插入站点互跳链接,支持路径跟随
// @author 自定义
// @match *://qoj.ac/*
// @match *://relia.uk/*
// @match *://huang.lt/*
// @match *://jiang.ly/*
// @grant none
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
// 配置互跳站点(域名+简洁显示名,贴合原生导航风格)
const SITES = [
{ domain: 'qoj.ac', name: 'qoj.ac' },
{ domain: 'huang.lt', name: 'huang.lt' },
{ domain: 'jiang.ly', name: 'jiang.ly' },
{ domain: 'relia.uk', name: 'relia.uk' },
];
// 获取当前页面核心信息(域名+完整路径,含参数)
const currentHost = window.location.host;
const fullPath = window.location.pathname + window.location.search;
// 核心:定位原生导航容器(匹配站点导航栏特征,兼容4个站点)
// 适配QOJ系列站点导航栏的常见选择器,优先级从高到低
let navContainer = null;
const selectors = [
'nav.navbar',
'.nav',
'ul[class*="nav"]',
'div[class*="nav"]',
'header nav'
];
// 遍历选择器,找到真实的导航容器
for (const sel of selectors) {
navContainer = document.querySelector(sel);
if (navContainer) break;
}
// 兜底:若未匹配到导航容器,定位包含目标导航文字的父容器
if (!navContainer) {
const navText = document.querySelector('a:contains("比赛"), a:contains("题库"), a:contains("博客")');
if (navText) navContainer = navText.closest('ul, div, nav');
}
// 未找到导航容器则终止(避免脚本报错)
if (!navContainer) return;
// 创建互跳链接外层容器(行内布局,贴合原生导航)
const jumpWrap = document.createElement('div');
jumpWrap.style.cssText = `
display: inline-flex;
align-items: center;
gap: 16px;
margin-left: 20px;
padding-left: 20px;
border-left: 1px solid #e5e7eb;
height: 100%;
`;
// 生成每个站点的跳转链接(样式贴合原生导航)
SITES.forEach(site => {
const a = document.createElement('a');
a.href = `https://${site.domain}${fullPath}`;
a.textContent = site.name;
// 关键:样式与站点原生导航链接完全一致,无突兀感
a.style.cssText = `
color: ${site.domain === currentHost ? '#2563eb' : '#374151'};
text-decoration: none;
font-size: inherit;
font-weight: ${site.domain === currentHost ? '600' : '400'};
padding: 8px 0;
line-height: inherit;
transition: color 0.2s;
`;
// 自身站点禁用点击,避免无效自跳转
if (site.domain === currentHost) {
a.style.pointerEvents = 'none';
a.style.opacity = '0.9';
}
// 悬浮效果匹配原生导航风格
a.onmouseover = () => {
if (site.domain !== currentHost) a.style.color = '#2563eb';
};
a.onmouseout = () => {
if (site.domain !== currentHost) a.style.color = '#374151';
};
jumpWrap.appendChild(a);
});
// 核心:将互跳容器插入到原生导航栏的【最后一个子元素后方】
// 即「比赛、比赛归档、题库...博客」的右侧,完美符合需求
navContainer.appendChild(jumpWrap);
})();
CF perf Graph
// ==UserScript==
// @name Codeforces Contest Perf Chart (Enhanced)
// @namespace http://tampermonkey.net/
// @version 2.2.0
// @description 插入Perf列并绘制折线图。支持过滤非计分赛,前6场不显示Perf折线。
// @author Gemini Help
// @match https://codeforces.com/contests/with/*
// @grant GM_addStyle
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
let hasAddedPerfColumn = false;
let hasDrawnChart = false;
const ratingColors = [
{ min: 0, max: 1199, color: '#808080' },
{ min: 1200, max: 1399, color: '#008000' },
{ min: 1400, max: 1599, color: '#03a89e' },
{ min: 1600, max: 1899, color: '#0000ff' },
{ min: 1900, max: 2099, color: '#aa00aa' },
{ min: 2100, max: 2299, color: '#ff8c00' },
{ min: 2300, max: 2399, color: '#ff8c00' },
{ min: 2400, max: 2599, color: '#ff0000' },
{ min: 2600, max: 2999, color: '#ff0000' },
{ min: 3000, max: Infinity, color: '#cc0000' }
];
function loadChartJS(callback) {
if (window.Chart) { callback(); return; }
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js';
script.onload = callback;
document.head.appendChild(script);
}
function extractNumber(text) {
if (!text || text.trim() === "" || text.includes('—')) return null;
const match = text.trim().match(/([+-−]?\d+)/);
return match ? parseInt(match[1].replace('−', '-')) : null;
}
function getRatingColor(value) {
for (const level of ratingColors) {
if (value >= level.min && value <= level.max) return level.color;
}
return '#000000';
}
function addPerfColumn() {
if (hasAddedPerfColumn) return;
const contestTable = document.querySelector('.user-contests-table');
if (!contestTable) return;
const headerRow = contestTable.querySelector('thead tr');
const newRatingTh = headerRow.querySelectorAll('th')[6];
const perfTh = document.createElement('th');
perfTh.className = 'top header';
perfTh.style = 'padding-right: 1.5em;';
perfTh.textContent = 'Perf';
headerRow.insertBefore(perfTh, newRatingTh);
const bodyRows = Array.from(contestTable.querySelectorAll('tbody tr'));
/* === 预扫描:找所有有 rating change 的行 === */
const ratedRows = bodyRows.filter(row => {
const tds = row.querySelectorAll('td');
if (tds.length < 7) return false;
const ratingChange = extractNumber(tds[5].textContent);
const newRating = extractNumber(tds[6].textContent);
return ratingChange !== null && newRating !== null;
});
/* === 判断是否需要隐藏前 6 场 === */
let hideFirstSix = false;
if (ratedRows.length > 0) {
const tds = ratedRows[0].querySelectorAll('td');
const ratingChange = extractNumber(tds[5].textContent);
const newRating = extractNumber(tds[6].textContent);
const oldRating = newRating - ratingChange;
if (oldRating !== 1500) {
hideFirstSix = true;
}
}
/* === 插入 Perf 列 === */
let ratedIndex = 0;
bodyRows.forEach(row => {
const tds = row.querySelectorAll('td');
if (tds.length < 7) return;
const ratingChange = extractNumber(tds[5].textContent);
const newRating = extractNumber(tds[6].textContent);
const perfTd = document.createElement('td');
perfTd.className = tds[5].className;
perfTd.style.fontWeight = 'bold';
if (ratingChange === null || newRating === null) {
perfTd.textContent = '—';
perfTd.style.color = 'inherit';
} else {
if (hideFirstSix && ratedIndex + 6 >= ratedRows.length) {
perfTd.textContent = '—';
perfTd.style.color = 'inherit';
} else {
const perfValue = newRating + 3 * ratingChange;
const perfStr = perfValue.toString();
const color = getRatingColor(perfValue);
if (perfValue >= 3000) {
perfTd.innerHTML =
`<span style="color:#000">${perfStr[0]}</span>` +
`<span style="color:#cc0000">${perfStr.slice(1)}</span>`;
} else {
perfTd.style.color = color;
perfTd.textContent = perfValue;
}
}
ratedIndex++;
}
row.insertBefore(perfTd, tds[6]);
});
hasAddedPerfColumn = true;
}
function getMonthNum(monthAbbr) {
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
return (months.indexOf(monthAbbr) + 1).toString().padStart(2, '0');
}
function extractChartData() {
const contestTable = document.querySelector('.user-contests-table');
if (!contestTable) return [];
const rawData = [];
const bodyRows = contestTable.querySelectorAll('tbody tr');
bodyRows.forEach(row => {
const tds = row.querySelectorAll('td');
if (tds.length < 8) return;
const ratingChange = extractNumber(tds[5].textContent);
const newRating = extractNumber(tds[7].textContent);
if (ratingChange !== null && newRating !== null) {
const rawText = tds[2].textContent.trim();
// 匹配格式如: "Jan/20/2024"
const dateMatch = rawText.match(/([a-zA-Z]+)\/(\d+)\/(\d+)/);
if (dateMatch) {
const [_, monthAbbr, day, year] = dateMatch;
const date = new Date(`${year}-${getMonthNum(monthAbbr)}-${day.padStart(2, '0')}`);
// --- 修改点:将 label 修改为包含年份的格式 ---
// 如果你想要 "24/Jan/20" 这种简写,可以使用 year.slice(-2)
const displayLabel = `${year}/${monthAbbr}/${day}`;
rawData.push({
date: date,
label: displayLabel,
rating: newRating,
perf: newRating + 3 * ratingChange
});
}
}
});
const sortedData = rawData.sort((a, b) => a.date - b.date);
return sortedData.map((item, index) => ({
...item,
perf: (index < 6) ? null : item.perf
}));
}
function drawChart() {
if (hasDrawnChart) return;
const chartData = extractChartData();
if (chartData.length === 0) return;
const labels = chartData.map(d => d.label);
const ratingPoints = chartData.map(d => d.rating);
const perfPoints = chartData.map(d => d.perf);
const allValues = [...ratingPoints, ...perfPoints].filter(v => v !== null);
const yMax = Math.max(...allValues) + 100;
const chartContainer = document.createElement('div');
GM_addStyle(`#rating-perf-chart { width: 95%; height: 400px; margin: 20px auto; border: 1px solid #e1e1e1; padding: 10px; background: #fff; }`);
chartContainer.id = 'rating-perf-chart';
const tableWrapper = document.querySelector('.datatable');
tableWrapper.parentNode.insertBefore(chartContainer, tableWrapper);
const ctx = document.createElement('canvas');
chartContainer.appendChild(ctx);
new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [
{
label: 'New Rating',
data: ratingPoints,
borderColor: '#2196F3',
backgroundColor: '#2196F380',
borderWidth: 2,
tension: 0.1,
spanGaps: true
},
{
label: 'Perf (Post-6th)',
data: perfPoints,
borderColor: '#4CAF50',
backgroundColor: '#4CAF5080',
borderWidth: 2,
tension: 0.1,
spanGaps: false // 设置为 false,使得前六场 null 值处不连线
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: { beginAtZero: false, max: yMax },
x: { grid: { display: false } }
},
plugins: {
tooltip: { mode: 'index', intersect: false }
}
},
plugins: [{
id: 'rating-bg',
beforeDraw: (chart) => {
const { ctx, chartArea, scales: { y } } = chart;
ratingColors.forEach(level => {
if (level.min > yMax) return;
const yTop = y.getPixelForValue(Math.min(level.max, yMax));
const yBottom = y.getPixelForValue(level.min);
ctx.fillStyle = level.color + '15';
ctx.fillRect(chartArea.left, yTop, chartArea.width, yBottom - yTop);
});
}
}]
});
hasDrawnChart = true;
}
function init() {
addPerfColumn();
loadChartJS(drawChart);
}
const observer = new MutationObserver(() => {
if (document.querySelector('.user-contests-table')) {
init();
observer.disconnect();
}
});
observer.observe(document.body, { childList: true, subtree: true });
setTimeout(init, 2000);
})();
洛谷奖项盒人
// ==UserScript==
// @name OIerDb 查询代码生成器
// @namespace http://tampermonkey.net/
// @version 4.0
// @description 根据洛谷获奖信息生成 OIerDb 查询代码
// @author Assistant
// @match https://www.luogu.com.cn/user/*
// @grant none
// @run-at document-idle
// ==/UserScript==
(function() {
'use strict';
/**
* 洛谷比赛显示名 → OIerDb 查询配置
* 基于洛谷官方支持认证比赛列表 + 137条contests数据验证
* 官方列表: https://www.luogu.com.cn/cert/contests
*/
const CONTEST_CONFIG = {
// ==================== CSP 系列 (2019-2025) ====================
'CSP-J': {
type: 'CSP入门',
name: (year) => `CSP${year}入门`,
years: [2019, 2020, 2021, 2022, 2023, 2024, 2025]
},
'CSP-S': {
type: 'CSP提高',
name: (year) => `CSP${year}提高`,
years: [2019, 2020, 2021, 2022, 2023, 2024, 2025]
},
// ==================== NOIP 系列 ====================
// 2020年起:NOIP (type: NOIP, name: NOIP{year})
'NOIP': {
type: (year) => year >= 2020 ? 'NOIP' : 'NOIP提高',
name: (year) => year >= 2020 ? `NOIP${year}` : `NOIP${year}提高`,
years: [2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2020, 2021, 2022, 2023, 2024, 2025]
// 注意:2019年无NOIP,被CSP替代
},
// 2013-2018:NOIP 普及组 (type: NOIP普及)
'NOIP 普及组': {
type: 'NOIP普及',
name: (year) => `NOIP${year}普及`,
years: [2013, 2014, 2015, 2016, 2017, 2018]
},
// 兼容旧显示名(部分页面可能显示为"NOIP 普及")
'NOIP 普及': {
type: 'NOIP普及',
name: (year) => `NOIP${year}普及`,
years: [2013, 2014, 2015, 2016, 2017, 2018]
},
// 2008-2018:NOIP 提高组 (type: NOIP提高)
'NOIP 提高组': {
type: 'NOIP提高',
name: (year) => `NOIP${year}提高`,
years: [2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018]
},
// ==================== NOI 系列 ====================
'NOI': {
type: 'NOI',
name: (year) => `NOI${year}`,
years: [2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025]
},
'NOI 冬令营': {
type: 'WC',
name: (year) => `WC${year}`,
years: [2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025, 2026]
},
'NOI 夏令营': {
// 2010-2020: name为 NOI{year}D类
// 2021-2025: name为 NOI{year}夏令营
type: 'NOID类',
name: (year) => year <= 2020 ? `NOI${year}D类` : `NOI${year}夏令营`,
years: [2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025]
},
'NOI 春季测试': {
type: 'NOIST',
name: (year) => `春季测试${year}`,
years: [2023]
},
'NOI 女生竞赛': {
type: 'NOI女生竞赛',
name: (year) => `NOI${year}女生竞赛`,
years: [2022, 2023, 2024, 2025, 2026]
},
// ==================== 其他国赛 ====================
'CTSC': {
type: 'CTSC',
// 2019年特殊:name是"CTS2019"而非"CTSC2019"
name: (year) => year === 2019 ? `CTS${year}` : `CTSC${year}`,
years: [2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019]
// 2020年起CTSC停办
},
'APIO': {
type: 'APIO',
name: (year) => `APIO${year}`,
years: [2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025]
},
'APIO 线上': {
type: 'APIO',
name: (year) => `APIO${year}线上`,
years: [2022, 2023]
},
'IOI': {
type: 'IOI',
name: (year) => `IOI${year}`,
years: [2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025]
},
'NGOI': {
type: 'NGOI',
name: (year) => `NGOI${year}`,
years: [2022, 2023, 2024, 2025, 2026]
}
};
/**
* 解析洛谷奖项数据(从 lentille-context)
*/
function parsePrizes() {
try {
const ctx = JSON.parse(document.getElementById('lentille-context')?.textContent || '{}');
const prizes = ctx?.data?.prizes;
if (Array.isArray(prizes)) {
return prizes
.map(p => p.prize)
.filter(p => p?.year && p?.contest && p?.prize);
}
} catch (e) {
console.warn('解析 lentille-context 失败:', e);
}
return [];
}
/**
* 模糊匹配比赛名称(处理显示名差异)
*/
function findContestConfig(contestName) {
// 精确匹配
if (CONTEST_CONFIG[contestName]) {
return { key: contestName, config: CONTEST_CONFIG[contestName] };
}
// 模糊匹配(处理空格、简写等差异)
for (const [key, config] of Object.entries(CONTEST_CONFIG)) {
// 去除空格后比较
if (contestName.replace(/\s/g, '') === key.replace(/\s/g, '')) {
return { key, config };
}
// 包含匹配
if (contestName.includes(key) || key.includes(contestName)) {
return { key, config };
}
}
return null;
}
/**
* 将单个奖项转换为 OIerDb 查询条件
*/
function prizeToCondition(prize) {
const { year, contest, prize: level } = prize;
const match = findContestConfig(contest);
// 未知比赛:降级处理
if (!match) {
console.warn(`⚠️ 未知比赛: "${contest}",使用 name 包含匹配`);
const safeContest = contest.replace(/'/g, "\\'");
return `oier.records.some(r => r.contest.name.includes('${safeContest}') && r.contest.year === ${year} && r.level === '${level}')`;
}
const { key, config } = match;
// 验证年份是否在支持范围内
if (config.years && !config.years.includes(year)) {
console.warn(`⚠️ ${key} 不支持 ${year} 年,但仍尝试生成查询`);
}
// 解析 type(可能是函数)
const contestType = typeof config.type === 'function' ? config.type(year) : config.type;
// 解析 name(可能是函数)
const contestName = typeof config.name === 'function' ? config.name(year) : config.name;
// 生成精确匹配条件
return `oier.records.some(r => r.contest.type === '${contestType}' && r.contest.name === '${contestName}' && r.level === '${level}')`;
}
/**
* 生成最终查询代码
*/
function generateCode(prizes) {
if (!prizes.length) return '/* 无有效奖项数据 */';
const conditions = prizes.map(prizeToCondition);
return `/** @param {OIerDb} db */
export default (db) => {
return db.oiers.filter((oier) => {
// 匹配所有奖项(AND 逻辑)
return ${conditions.join(' &&\n ')};
});
}`;
}
/**
* 注入按钮到页面
*/
function injectButton() {
const header = document.querySelector('h3:has(svg[data-icon="award"])');
if (!header) return false;
if (header.querySelector('#oierdb-gen-btn')) return true;
const btn = document.createElement('button');
btn.id = 'oierdb-gen-btn';
btn.innerText = '🔗 生成 OIerDb 查询';
btn.style.cssText = `
float:right; margin-right:8px; padding:4px 10px; font-size:12px;
cursor:pointer; background:linear-gradient(135deg,#667eea,#764ba2);
color:white; border:none; border-radius:4px;
box-shadow:0 2px 4px rgba(0,0,0,0.2); transition:opacity 0.2s;
`;
btn.onmouseenter = () => btn.style.opacity = '0.9';
btn.onmouseleave = () => btn.style.opacity = '1';
const infoLink = header.querySelector('a[href="javascript:void 0"]');
if (infoLink) header.insertBefore(btn, infoLink);
else header.appendChild(btn);
btn.onclick = () => {
const prizes = parsePrizes();
if (!prizes.length) {
alert('⚠️ 未找到奖项数据,请确保页面已完全加载');
return;
}
const code = generateCode(prizes);
// 输出调试信息
console.group('📋 OIerDb 查询代码生成详情');
console.log('🏆 解析到的奖项:');
prizes.forEach((p, i) => {
const match = findContestConfig(p.contest);
console.log(` [${i}] ${p.year} ${p.contest} ${p.prize}`);
if (match) {
const type = typeof match.config.type === 'function' ? match.config.type(p.year) : match.config.type;
const name = typeof match.config.name === 'function' ? match.config.name(p.year) : match.config.name;
console.log(` → type: "${type}", name: "${name}"`);
}
});
console.log('\n📝 生成的代码已复制到剪贴板');
console.groupEnd();
navigator.clipboard.writeText(code).then(() => {
const oldText = btn.innerText;
btn.innerText = '✅ 已复制!';
btn.style.background = '#10b981';
setTimeout(() => {
btn.innerText = oldText;
btn.style.background = '';
}, 1500);
}).catch(() => {
prompt('复制失败,请手动复制:', code);
});
};
return true;
}
// 初始化:SPA 兼容
function init() {
if (injectButton()) return;
const observer = new MutationObserver((mutations, obs) => {
if (injectButton()) obs.disconnect();
});
observer.observe(document.body, { childList: true, subtree: true });
setTimeout(() => observer.disconnect(), 30000);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();

浙公网安备 33010602011771号