// ==UserScript==
// @name 52pj帖子列表增强
// @match https://www.52pojie.cn/*
// @version 2.0
// @description 实时监测并修改链接样式,集成可视化设置面板(支持回帖奖励、悬赏已解决、悬赏金额动态样式)
// @author aura service
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_getValue
// @license ISC
// ==/UserScript==
(function() {
'use strict';
// ---------- 配置存储 ----------
// 回帖奖励样式
const defaultStyles = { fontWeight: 'bold', color: 'red', fontSize: '16px' };
let styles = {
fontWeight: GM_getValue('fontWeight', defaultStyles.fontWeight),
color: GM_getValue('color', defaultStyles.color),
fontSize: GM_getValue('fontSize', defaultStyles.fontSize)
};
// 悬赏已解决配置
const defaultResolvedAction = 'mark';
const defaultResolvedBgColor = '#f0f0f0';
let resolvedAction = GM_getValue('resolvedAction', defaultResolvedAction);
let resolvedBgColor = GM_getValue('resolvedBgColor', defaultResolvedBgColor);
// 悬赏金额动态样式配置
const REWARD_ENABLED_KEY = 'rewardEnabled';
const REWARD_RULES_KEY = 'rewardRules';
const defaultRewardRules = [
{ min: 1, max: 9, style: { color: 'green' } },
{ min: 10, max: 49, style: { color: 'orange', fontWeight: 'bold' } },
{ min: 50, max: 99, style: { color: 'red', fontWeight: 'bold', fontSize: '16px' } },
{ min: 100, max: null, style: { color: 'purple', fontWeight: 'bold', fontSize: '18px', backgroundColor: '#ffffcc' } }
];
let rewardEnabled = GM_getValue(REWARD_ENABLED_KEY, false);
let rewardRules = GM_getValue(REWARD_RULES_KEY, defaultRewardRules);
// ---------- 辅助函数 ----------
function getStyleForReward(amount) {
if (!rewardEnabled) return null;
for (const rule of rewardRules) {
if (amount >= rule.min && (rule.max === null || amount <= rule.max)) {
return rule.style;
}
}
return null;
}
// ---------- 核心处理函数(同之前)----------
function checkAndModify() {
const rows = document.querySelectorAll('tbody tr');
rows.forEach(row => {
// 回帖奖励
const ths = row.querySelectorAll('th.common, th.new');
let hasReward = false;
ths.forEach(th => {
const span = th.querySelector('span');
if (span && span.textContent.includes('回帖奖励')) hasReward = true;
});
if (hasReward) {
const links = row.querySelectorAll('th.common a.s.xst, th.new a.s.xst');
links.forEach(link => {
link.style.fontWeight = styles.fontWeight;
link.style.color = styles.color;
link.style.fontSize = styles.fontSize;
});
}
// 悬赏已解决标记/隐藏
const rewardIcon = row.querySelector('td.icn img[src*="rewardsmall.gif"]');
if (rewardIcon) {
const resolvedLink = Array.from(row.querySelectorAll('th a')).find(a => a.textContent.trim() === '[已解决]');
if (resolvedLink) {
if (resolvedAction === 'mark') row.style.backgroundColor = resolvedBgColor;
else if (resolvedAction === 'hide') row.style.display = 'none';
}
}
// 悬赏金额动态样式
if (rewardEnabled && rewardIcon) {
const rewardLink = row.querySelector('th a[href*="specialtype=reward"]');
if (rewardLink) {
const amountSpan = rewardLink.querySelector('span.xw1');
if (amountSpan) {
const amount = parseInt(amountSpan.textContent.trim(), 10);
if (!isNaN(amount)) {
const style = getStyleForReward(amount);
if (style) {
const titleLink = row.querySelector('th.common a.s.xst, th.new a.s.xst');
if (titleLink) {
for (const [prop, val] of Object.entries(style)) {
titleLink.style[prop] = val;
}
}
}
}
}
}
}
});
}
// ---------- 设置面板 ----------
let settingsPanel = null;
function createSettingsPanel() {
if (settingsPanel) {
settingsPanel.style.display = settingsPanel.style.display === 'none' ? 'block' : 'none';
return;
}
// 创建面板容器
const panel = document.createElement('div');
panel.id = 'pj-settings-panel';
Object.assign(panel.style, {
position: 'fixed',
top: '60px',
right: '20px',
width: '400px',
maxHeight: '80vh',
overflowY: 'auto',
backgroundColor: '#fff',
border: '1px solid #ccc',
borderRadius: '8px',
boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
padding: '16px',
zIndex: '9999',
fontFamily: 'Arial, sans-serif',
fontSize: '14px',
display: 'block'
});
// 标题 + 关闭按钮
const header = document.createElement('div');
Object.assign(header.style, {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '16px',
paddingBottom: '8px',
borderBottom: '1px solid #eee'
});
header.innerHTML = '
52pj 帖子增强设置
';
const closeBtn = document.createElement('button');
closeBtn.textContent = '✕';
Object.assign(closeBtn.style, {
background: 'none',
border: 'none',
fontSize: '18px',
cursor: 'pointer',
padding: '0 6px'
});
closeBtn.onclick = () => panel.style.display = 'none';
header.appendChild(closeBtn);
panel.appendChild(header);
// ---------- 回帖奖励样式 ----------
const section1 = document.createElement('div');
section1.innerHTML = '回帖奖励样式
';
const form1 = document.createElement('div');
form1.style.display = 'grid';
form1.style.gridTemplateColumns = '80px 1fr';
form1.style.gap = '8px';
form1.style.marginBottom = '8px';
// 字体粗细
form1.appendChild(document.createTextNode('字体粗细:'));
const inputWeight = document.createElement('input');
inputWeight.type = 'text';
inputWeight.value = styles.fontWeight;
form1.appendChild(inputWeight);
// 颜色
form1.appendChild(document.createTextNode('颜色:'));
const inputColor = document.createElement('input');
inputColor.type = 'text';
inputColor.value = styles.color;
form1.appendChild(inputColor);
// 字体大小
form1.appendChild(document.createTextNode('字体大小:'));
const inputSize = document.createElement('input');
inputSize.type = 'text';
inputSize.value = styles.fontSize;
form1.appendChild(inputSize);
section1.appendChild(form1);
panel.appendChild(section1);
// ---------- 悬赏已解决处理 ----------
const section2 = document.createElement('div');
section2.innerHTML = '悬赏已解决处理
';
const form2 = document.createElement('div');
form2.style.marginBottom = '8px';
// 处理方式单选
const radioNone = document.createElement('input');
radioNone.type = 'radio';
radioNone.name = 'resolvedAction';
radioNone.value = 'none';
radioNone.checked = resolvedAction === 'none';
const labelNone = document.createTextNode('无操作 ');
const radioMark = document.createElement('input');
radioMark.type = 'radio';
radioMark.name = 'resolvedAction';
radioMark.value = 'mark';
radioMark.checked = resolvedAction === 'mark';
const labelMark = document.createTextNode('标记背景 ');
const radioHide = document.createElement('input');
radioHide.type = 'radio';
radioHide.name = 'resolvedAction';
radioHide.value = 'hide';
radioHide.checked = resolvedAction === 'hide';
const labelHide = document.createTextNode('隐藏整行 ');
form2.appendChild(radioNone);
form2.appendChild(labelNone);
form2.appendChild(radioMark);
form2.appendChild(labelMark);
form2.appendChild(radioHide);
form2.appendChild(labelHide);
form2.appendChild(document.createElement('br'));
// 背景色
const bgColorLabel = document.createTextNode('背景色: ');
const inputBgColor = document.createElement('input');
inputBgColor.type = 'text';
inputBgColor.value = resolvedBgColor;
inputBgColor.style.width = '100px';
form2.appendChild(bgColorLabel);
form2.appendChild(inputBgColor);
section2.appendChild(form2);
panel.appendChild(section2);
// ---------- 悬赏金额动态样式 ----------
const section3 = document.createElement('div');
section3.innerHTML = '悬赏金额动态样式
';
const form3 = document.createElement('div');
form3.style.marginBottom = '8px';
// 启用开关
const enableCheck = document.createElement('input');
enableCheck.type = 'checkbox';
enableCheck.checked = rewardEnabled;
const enableLabel = document.createTextNode(' 启用');
form3.appendChild(enableCheck);
form3.appendChild(enableLabel);
form3.appendChild(document.createElement('br'));
// 规则编辑(JSON文本域)
const rulesLabel = document.createTextNode('规则 (JSON):');
form3.appendChild(rulesLabel);
form3.appendChild(document.createElement('br'));
const rulesTextarea = document.createElement('textarea');
rulesTextarea.value = JSON.stringify(rewardRules, null, 2);
Object.assign(rulesTextarea.style, {
width: '100%',
height: '150px',
fontFamily: 'monospace',
fontSize: '12px',
marginTop: '4px'
});
form3.appendChild(rulesTextarea);
// 重置默认规则按钮
const resetBtn = document.createElement('button');
resetBtn.textContent = '恢复默认规则';
resetBtn.style.marginTop = '4px';
resetBtn.onclick = () => {
rulesTextarea.value = JSON.stringify(defaultRewardRules, null, 2);
};
form3.appendChild(resetBtn);
section3.appendChild(form3);
panel.appendChild(section3);
// ---------- 保存/取消按钮 ----------
const btnGroup = document.createElement('div');
btnGroup.style.display = 'flex';
btnGroup.style.justifyContent = 'flex-end';
btnGroup.style.gap = '8px';
btnGroup.style.marginTop = '20px';
const saveBtn = document.createElement('button');
saveBtn.textContent = '保存';
Object.assign(saveBtn.style, {
padding: '6px 16px',
backgroundColor: '#4CAF50',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
});
saveBtn.onclick = () => {
// 回帖奖励样式
styles.fontWeight = inputWeight.value;
styles.color = inputColor.value;
styles.fontSize = inputSize.value;
GM_setValue('fontWeight', styles.fontWeight);
GM_setValue('color', styles.color);
GM_setValue('fontSize', styles.fontSize);
// 悬赏已解决
const selectedAction = document.querySelector('input[name="resolvedAction"]:checked')?.value;
if (selectedAction) {
resolvedAction = selectedAction;
GM_setValue('resolvedAction', resolvedAction);
}
resolvedBgColor = inputBgColor.value;
GM_setValue('resolvedBgColor', resolvedBgColor);
// 悬赏金额
rewardEnabled = enableCheck.checked;
GM_setValue(REWARD_ENABLED_KEY, rewardEnabled);
try {
const parsed = JSON.parse(rulesTextarea.value);
if (Array.isArray(parsed) && parsed.every(r => typeof r.min === 'number' && r.style)) {
rewardRules = parsed;
GM_setValue(REWARD_RULES_KEY, rewardRules);
} else {
alert('规则格式无效,请检查后重试');
return;
}
} catch (e) {
alert('JSON 解析失败: ' + e.message);
return;
}
alert('设置已保存!');
panel.style.display = 'none';
// 立即重新应用样式
checkAndModify();
};
const cancelBtn = document.createElement('button');
cancelBtn.textContent = '关闭';
Object.assign(cancelBtn.style, {
padding: '6px 16px',
backgroundColor: '#f44336',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
});
cancelBtn.onclick = () => panel.style.display = 'none';
btnGroup.appendChild(saveBtn);
btnGroup.appendChild(cancelBtn);
panel.appendChild(btnGroup);
document.body.appendChild(panel);
settingsPanel = panel;
}
// ---------- 注册菜单命令 ----------
GM_registerMenuCommand("⚙️ 打开设置面板", createSettingsPanel);
// ---------- 启动核心功能 ----------
const observer = new MutationObserver(checkAndModify);
observer.observe(document.body, { childList: true, subtree: true });
checkAndModify();
setInterval(checkAndModify, 1000);
})();