// ==UserScript==
// @name N诺错题本刷题
// @namespace https://bbs.tampermonkey.net.cn/
// @version 0.4.4
// @description Navigate through error list with floating panel, keyboard shortcuts, auto-navigation, and error list update notifications
// @author You
// @match https://noobdream.com/Practice/*
// @resource noobdream-errorlist https://noobdream.com/Practice/pro_err_list/
// @grant GM_getResourceText
// @grant GM_setValue
// @grant GM_getValue
// @license MIT
// ==/UserScript==
//this is a userscript for tampermonkey
//it will run on the page https://noobdream.com/Practice/error_list/*
(function () {
"use strict";
// 使用正确的变量声明
const translate = {
数据结构: "datastruct",
操作系统: "system",
计算机网络: "network",
计算机组成原理: "organization",
};
const myHeaders = ()=>{
var myHeader = new Headers();
myHeader.append(
"Cookie",
// "csrftoken=kSI1aO9DwC2H6YDRwvyfjluYPJnyC1ByoPIRXBrFyaFX0b4MggnLUD6xjb4zQksf"
//get cookie "csrftoken" from current page
"csrftoken=" +
document.cookie
.split("; ")
.find((row) => row.startsWith("csrftoken="))
.split("=")[1]
);
return myHeader;
}
const shuffle = (array, seed) => {
let currentIndex = array.length, temporaryValue, randomIndex;
seed = seed || 1;
let random = function() {
var x = Math.sin(seed++) * 10000;
return x - Math.floor(x);
};
// While there remain elements to shuffle...
while (0 !== currentIndex) {
// Pick a remaining element...
randomIndex = Math.floor(random() * currentIndex);
currentIndex -= 1;
// And swap it with the current element.
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
}
const refresh_tuples = () => {
const pro_error_list = new DOMParser().parseFromString(
GM_getResourceText("noobdream-errorlist"),
"text/html"
);
if (!pro_error_list) return;
// Get all elements with class "tag" inside the container
const tags = Array.from(pro_error_list.querySelectorAll(".tag"));
// Collect tuples
const tuples = [];
for (let i = 0; i < tags.length; i += 3) {
if (tags[i] && tags[i + 1]) {
const problem_id = tags[i].textContent.trim();
const problem_class = tags[i + 1].textContent.trim();
tuples.push([problem_id, translate[problem_class]]);
}
}
return shuffle(tuples,tuples.legth);
};
// 获取题目列表
const tuples = refresh_tuples() || [];
// 检查错题本是否有更新
const currentErrorCount = tuples.length;
const cachedErrorCount = GM_getValue("error_table_ver", -1);
const hasUpdates =
cachedErrorCount !== -1 && cachedErrorCount !== currentErrorCount;
// 如果是首次使用,初始化错题数量缓存
if (cachedErrorCount === -1) {
GM_setValue("error_table_ver", currentErrorCount);
}
// 获取当前题目和索引
let current = GM_getValue("current_error_id", tuples[0] || ["", ""]);
let current_index = tuples.findIndex(
(tuple) => tuple[0] === current[0] && tuple[1] === current[1]
);
// 如果找不到当前题目,重置为第一个
if (current_index === -1 && tuples.length > 0) {
current_index = 0;
current = tuples[0];
GM_setValue("current_error_id", current);
}
// 用于存储跳转链接
let gotopage = "#";
// 创建控制面板 - 移动到右下角
const controlPanel = document.createElement("div");
controlPanel.style.position = "fixed";
controlPanel.style.bottom = "20px"; // 改为底部定位
controlPanel.style.left = "20px";
controlPanel.style.backgroundColor = "white";
controlPanel.style.padding = "12px";
controlPanel.style.border = "1px solid #ccc";
controlPanel.style.zIndex = "1000";
controlPanel.style.fontSize = "14px";
controlPanel.style.fontFamily = "Arial, sans-serif";
controlPanel.style.boxShadow = "0 2px 10px rgba(0,0,0,0.2)";
controlPanel.style.borderRadius = "8px";
controlPanel.style.display = "flex";
controlPanel.style.flexDirection = "column";
controlPanel.style.alignItems = "center";
controlPanel.style.gap = "8px";
controlPanel.style.minWidth = "200px";
controlPanel.style.opacity = "0.9";
controlPanel.style.transition = "opacity 0.3s";
// 鼠标悬停时增加不透明度
controlPanel.addEventListener("mouseenter", () => {
controlPanel.style.opacity = "1";
});
controlPanel.addEventListener("mouseleave", () => {
controlPanel.style.opacity = "0.9";
});
// 创建按钮样式函数
const styleButton = (button) => {
button.style.padding = "6px 12px";
button.style.cursor = "pointer";
button.style.backgroundColor = "#f0f0f0";
button.style.border = "1px solid #ccc";
button.style.borderRadius = "4px";
button.style.margin = "0 4px";
button.style.transition = "background-color 0.2s";
button.addEventListener("mouseenter", () => {
button.style.backgroundColor = "#e0e0e0";
});
button.addEventListener("mouseleave", () => {
button.style.backgroundColor = "#f0f0f0";
});
return button;
};
// 创建当前题目显示
const currentDisplay = document.createElement("div");
currentDisplay.textContent =
tuples.length > 0
? `Current: ${current[0]} (${current[1]})`
: "No problems found";
currentDisplay.style.margin = "8px 0";
currentDisplay.style.fontWeight = "bold";
currentDisplay.style.textAlign = "center";
currentDisplay.style.width = "100%";
// 创建剩余题目显示
const remainDisplay = document.createElement("div");
remainDisplay.textContent =
tuples.length > 0
? `Remain: ${tuples.length - current_index - 1}`
: "No problems";
remainDisplay.style.margin = "5px 0";
remainDisplay.style.fontSize = "12px";
remainDisplay.style.color = "#666";
// 更新显示函数
const updateDisplay = (autoNavigate = false) => {
if (tuples.length === 0) return;
fetch(
`https://noobdream.com/Practice/${
tuples[current_index][1]
}/?keyname=${tuples[current_index][0].replace("P", "")}`,
{
method: "GET",
headers: myHeaders(),
redirect: "follow",
}
)
.then((response) => response.text())
.then((text) => {
const parser = new DOMParser();
const doc = parser.parseFromString(text, "text/html");
var link = doc.querySelector(
"body > div:nth-child(2) > div:nth-child(9) > div.OjInfo > div > div:nth-child(3) > a"
);
if (!link) {
//link=text.search(/Practice\/article\/121\/)
var reg = /\/Practice\/article\/\d+/
//var reg = /\d+/
link = reg.exec(text)[0].trim()
gotopage=link
if(!link){
alert("Link not found in the fetched page.\n"+link );
currentDisplay.textContent = `Error: ${current[0]} (${current[1]})`;
currentDisplay.style.color = "red";
return;
}
}else{
gotopage = link ? link.href : "#";
}
// 如果设置了自动导航,直接跳转到题目页面
if (autoNavigate && gotopage !== "#") {
window.location.href = gotopage;
return;
}
currentDisplay.textContent = `Current: ${current[0]} (${current[1]})`;
currentDisplay.style.color = "#0066cc";
currentDisplay.style.cursor = "pointer";
console.log("gotopage:", gotopage);
currentDisplay.onclick = () => {
window.location.href = gotopage; // 在当前标签页打开
};
remainDisplay.textContent = `Remain: ${
tuples.length - current_index - 1
}`;
})
.catch((err) => {
alert("Error fetching problem page:", err);
currentDisplay.textContent = `Error: ${current[0]} (${current[1]})`;
currentDisplay.style.color = "red";
});
};
// 创建上一题按钮
const previousButton = styleButton(document.createElement("button"));
previousButton.textContent = "Previous [";
previousButton.title = "快捷键: [";
previousButton.addEventListener("click", () => {
if (tuples.length > 0) {
current_index = (current_index - 1 + tuples.length) % tuples.length;
current = tuples[current_index];
GM_setValue("current_error_id", current);
updateDisplay(true); // 自动跳转
}
});
// 创建下一题按钮
const nextButton = styleButton(document.createElement("button"));
nextButton.textContent = "Next ]";
nextButton.title = "快捷键: ]";
nextButton.addEventListener("click", () => {
if (tuples.length > 0) {
current_index = (current_index + 1) % tuples.length;
current = tuples[current_index];
GM_setValue("current_error_id", current);
updateDisplay(true); // 自动跳转
}
});
// 创建重置按钮
const resetButton = styleButton(document.createElement("button"));
resetButton.textContent = "Reset";
resetButton.style.fontSize = "12px";
resetButton.style.padding = "4px 8px";
resetButton.addEventListener("click", () => {
if (tuples.length > 0) {
current_index = 0;
const newTuples = refresh_tuples();
current = tuples[0];
GM_setValue("current_error_id", current);
if (newTuples && newTuples.length > 0) {
tuples.length = 0;
tuples.push(...newTuples);
// 更新错题数量缓存
GM_setValue("error_table_ver", newTuples.length);
// 隐藏更新提醒
if (updateNotification) {
updateNotification.style.display = "none";
}
}
updateDisplay();
}
});
// 创建按钮容器
const buttonContainer = document.createElement("div");
buttonContainer.style.display = "flex";
buttonContainer.style.justifyContent = "center";
buttonContainer.style.width = "100%";
buttonContainer.appendChild(previousButton);
buttonContainer.appendChild(nextButton);
// 创建更新提醒
const updateNotification = document.createElement("div");
updateNotification.style.display = hasUpdates ? "block" : "none";
updateNotification.style.backgroundColor = "#fff3cd";
updateNotification.style.color = "#856404";
updateNotification.style.padding = "6px";
updateNotification.style.borderRadius = "4px";
updateNotification.style.fontSize = "12px";
updateNotification.style.textAlign = "center";
updateNotification.style.width = "100%";
updateNotification.style.marginBottom = "8px";
updateNotification.style.border = "1px solid #ffeeba";
updateNotification.textContent = `错题本存在更新!当前: ${currentErrorCount}, 缓存: ${cachedErrorCount}`;
// 创建更新缓存按钮
const updateCacheButton = styleButton(document.createElement("button"));
updateCacheButton.textContent = "更新缓存";
updateCacheButton.style.fontSize = "12px";
updateCacheButton.style.padding = "4px 8px";
updateCacheButton.addEventListener("click", () => {
GM_setValue("error_table_ver", currentErrorCount);
updateNotification.style.display = "none";
alert(`缓存已更新!当前错题数量: ${currentErrorCount}`);
});
// 创建底部容器
const bottomContainer = document.createElement("div");
bottomContainer.style.display = "flex";
bottomContainer.style.justifyContent = "space-between";
bottomContainer.style.alignItems = "center";
bottomContainer.style.width = "100%";
bottomContainer.appendChild(remainDisplay);
// 创建右侧按钮容器
const rightButtonsContainer = document.createElement("div");
rightButtonsContainer.style.display = "flex";
rightButtonsContainer.style.gap = "4px";
rightButtonsContainer.appendChild(updateCacheButton);
rightButtonsContainer.appendChild(resetButton);
bottomContainer.appendChild(rightButtonsContainer);
// 将元素添加到控制面板
controlPanel.appendChild(currentDisplay);
if (hasUpdates) {
controlPanel.appendChild(updateNotification);
}
controlPanel.appendChild(buttonContainer);
controlPanel.appendChild(bottomContainer);
// 将控制面板添加到文档
document.body.appendChild(controlPanel);
// 添加键盘事件监听器
document.addEventListener("keydown", (event) => {
switch (event.key) {
case "[": {
// 视觉反馈
previousButton.style.backgroundColor = "#d0d0d0";
setTimeout(() => {
previousButton.style.backgroundColor = "#f0f0f0";
}, 200);
// 直接调用逻辑而不是点击按钮,避免重复触发
if (tuples.length > 0) {
current_index = (current_index - 1 + tuples.length) % tuples.length;
current = tuples[current_index];
GM_setValue("current_error_id", current);
updateDisplay(true); // 自动跳转
}
break;
}
case "]": {
// 视觉反馈
nextButton.style.backgroundColor = "#d0d0d0";
setTimeout(() => {
nextButton.style.backgroundColor = "#f0f0f0";
}, 200);
// 直接调用逻辑而不是点击按钮,避免重复触发
if (tuples.length > 0) {
current_index = (current_index + 1) % tuples.length;
current = tuples[current_index];
GM_setValue("current_error_id", current);
updateDisplay(true); // 自动跳转
}
break;
}
}
});
// 初始化显示
if (tuples.length > 0) {
updateDisplay();
}
})();