// ==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); })();