您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
论坛增强功能:AI内容总结、新标签页打开、用户屏蔽、快速预览、自定义CSS等
// ==UserScript== // @name 社区助手1.0.9 // @namespace https://boyshelpboys.com/ // @version 1.0.9 // @description 论坛增强功能:AI内容总结、新标签页打开、用户屏蔽、快速预览、自定义CSS等 // @author 全民制作人 // @match https://*.boyshelpboys.com/* // @icon https://boyshelpboys.com/upload/attach/202410/b.svg // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_xmlhttpRequest // @grant GM_notification // @connect api.siliconflow.cn // @connect boyshelpboys.com // @license MIT // @run-at document-end // ==/UserScript== (function() { 'use strict'; // 配置 const CONFIG = { apiUrl: 'https://api.siliconflow.cn/v1/chat/completions', defaultApiKey: 'sk-jantscvokoclrasijsgfdxuujcbikpebkzosubcjwmjteebu', defaultModel: 'THUDM/glm-4-9b-chat', checkInterval: 60000, // 通知检查间隔 (1分钟) maxSavedIds: 100, // 最大保存的帖子ID数量 maxNotifications: 3, // 最大同时显示的通知数 notificationTimeout: 20000 // 通知自动消失时间 (20秒) }; // 初始化 CSS GM_addStyle(` .blocked-post{display:none!important} #bhb-panel{position:fixed;right:20px;top:20px;background:#1a1a1a;border:1px solid #333;border-radius:4px;padding:8px;z-index:9999;box-shadow:0 2px 8px rgba(0,0,0,.3);transition:.3s;width:280px;color:#ccc;font-size:12px} #bhb-panel.mini{width:24px;height:24px;min-width:24px;padding:0;overflow:hidden;cursor:pointer} #bhb-panel.mini #bhb-content{display:none} #bhb-panel-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;padding-bottom:4px;border-bottom:1px solid #333} .bhb-section{margin-bottom:10px} .bhb-section-title{color:#888;font-size:11px;margin-bottom:6px;padding-left:2px} .bhb-options-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:6px;margin-bottom:6px} .bhb-option{position:relative} .bhb-option-content{display:flex;align-items:center;justify-content:space-between;gap:8px} .bhb-label{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} .bhb-checkbox-label{display:flex;align-items:center;gap:4px} .bhb-checkbox{margin:0;transform:scale(.9)} .bhb-input-section{display:grid;grid-template-columns:repeat(2,1fr);gap:6px} .bhb-input-area{display:none;margin-top:8px;background:#1a1a1a;border:1px solid #333;border-radius:4px;padding:8px} .bhb-input-area.active{display:block} .bhb-textarea{width:100%;height:100px;background:#222;border:1px solid #333;border-radius:2px;color:#ccc;padding:4px;font-size:12px;resize:vertical;margin-bottom:4px} .bhb-btn{background:#222;color:#ccc;border:1px solid #333;padding:0 8px;border-radius:2px;cursor:pointer;font-size:12px;text-align:center;white-space:nowrap;line-height:20px;height:20px;display:inline-flex;align-items:center;justify-content:center;width:100%} .bhb-btn:hover{background:#2a2a2a;color:#fff} .ai-summary-btn{background:#222;color:#ccc;border:1px solid #333;padding:4px 12px;border-radius:12px;cursor:pointer;margin:6px auto;font-size:12px;display:block;transition:all .2s ease} .ai-summary-btn:hover{background:#2a2a2a;color:#fff} .ai-summary{background:#1a1a1a;border:1px solid #333;border-radius:4px;padding:12px;margin:8px 0;color:#ccc;line-height:1.6;white-space:pre-line} .ai-loading{color:#888;text-align:center;padding:8px} .ai-summary.hide{display:none} #bhb-toggle{width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;color:#ccc;cursor:pointer} #bhb-toggle:hover{background:#222} #bhb-help-area{position:relative;width:100%;box-sizing:border-box;border-top:1px solid #333;margin-top:8px;z-index:9000} .bhb-help-content{max-height:120px;overflow-y:auto} #bhb-panel.mini #bhb-toggle{display:flex} .bhb-tip{font-size:10px;color:#666;margin-top:2px} .auto-pagination-loader{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);background:#1a1a1a;color:#ccc;padding:5px 10px;border-radius:4px;font-size:12px;z-index:999;opacity:0;transition:opacity 0.3s ease;} .auto-pagination-loader.show{opacity:1;} .bhb-page-marker{text-align:center;width:100%;display:block;padding:5px 0;color:#888;font-size:12px;} /* 快速预览样式 - 左侧固定 */ .bhb-preview-container{position:fixed;z-index:10000;width:300px;left:0;top:0;bottom:0;background:#1a1a1a;border-right:1px solid #333;box-shadow:2px 0 10px rgba(0,0,0,.3);padding:0;overflow:hidden;opacity:0;visibility:hidden;transition:all .3s ease;transform:translateX(-100%)} .bhb-preview-container.show{opacity:1;visibility:visible;transform:translateX(0)} .bhb-preview-header{display:flex;justify-content:space-between;align-items:center;padding:8px 12px;background:#222;border-bottom:1px solid #333} .bhb-preview-title{font-size:14px;font-weight:bold;color:#fff;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1} .bhb-preview-info{font-size:12px;color:#888;margin-left:10px} .bhb-preview-content{padding:12px;overflow-y:auto;color:#ccc;line-height:1.5;font-size:13px;position:absolute;top:40px;bottom:40px;left:0;right:0;scrollbar-width:thin;scrollbar-color:#444 #222} .bhb-preview-content::-webkit-scrollbar{width:6px;height:6px} .bhb-preview-content::-webkit-scrollbar-track{background:#222} .bhb-preview-content::-webkit-scrollbar-thumb{background:#444;border-radius:3px} .bhb-preview-content::-webkit-scrollbar-thumb:hover{background:#555} .bhb-preview-footer{display:flex;justify-content:space-between;padding:8px 12px;border-top:1px solid #333;background:#222;font-size:12px;color:#888;position:absolute;bottom:0;left:0;right:0} .bhb-preview-loading{display:flex;justify-content:center;align-items:center;height:100px;color:#888} .bhb-preview-error{color:#ff6b6b;text-align:center;padding:20px} .bhb-preview-img{max-width:100%;height:auto;margin:8px 0;border-radius:2px} .bhb-preview-close{position:absolute;top:8px;right:8px;width:24px;height:24px;display:flex;align-items:center;justify-content:center;cursor:pointer;color:#888;font-size:18px;border-radius:50%;background:#333;z-index:1} .bhb-preview-close:hover{background:#444;color:#fff} /* 预览高亮样式 */ .bhb-preview-highlight{background:rgba(255,255,255,0.05);border-radius:2px;transition:background 0.2s ease} .bhb-preview-highlight:hover{background:rgba(255,255,255,0.1)} `); // 工具函数 const Utils = { $: (selector, parent = document) => parent.querySelector(selector), $$: (selector, parent = document) => [...parent.querySelectorAll(selector)], toggleClass: (el, className) => el.classList.toggle(className), createEl: (tag, props = {}) => { const el = document.createElement(tag); Object.entries(props).forEach(([k, v]) => { if (k === 'className') el.className = v; else if (k === 'innerHTML') el.innerHTML = v; else if (k === 'textContent') el.textContent = v; else if (k === 'onclick') el.onclick = v; else el.setAttribute(k, v); }); return el; }, // 简化的HTTP请求 request: (url, options = {}) => new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: options.method || 'GET', url, headers: options.headers || {}, data: options.data ? JSON.stringify(options.data) : undefined, timeout: options.timeout || 10000, onload: r => r.status >= 200 && r.status < 300 ? resolve(options.isJson ? JSON.parse(r.responseText) : r.responseText) : reject(new Error(`请求失败: ${r.status}`)), onerror: reject, ontimeout: () => reject(new Error('请求超时')) }); }), // 统一的通知发送 notify: (title, body, url, tag) => { if ("Notification" in window && Notification.permission === "granted") { const notification = new Notification(title, { body, icon: 'https://boyshelpboys.com/upload/attach/202410/b.svg', tag }); if (url) { notification.onclick = () => { window.open(url, '_blank'); notification.close(); }; } // 设置通知20秒后自动关闭 setTimeout(() => notification.close(), CONFIG.notificationTimeout); } else { GM_notification({ title, text: body, image: 'https://boyshelpboys.com/upload/attach/202410/b.svg', onclick: url ? () => window.open(url, '_blank') : null, timeout: CONFIG.notificationTimeout }); } } }; // 功能实现 const Features = { // 在新标签页打开帖子 openInNewTab: () => { Utils.$$('a.subject[href*="thread-"]').forEach(a => { a.target = '_blank'; a.rel = 'noopener'; }); }, // 自动翻页功能 - AJAX实现 autoPagination: { isLoading: false, nextPageLink: null, container: null, pageCount: 1, // 初始化自动翻页 init: () => { if (!GM_getValue('auto_pagination', false)) return; if (location.href.includes('/thread-') && !location.href.includes('/forum-')) return; // 创建加载提示元素 const loader = Utils.createEl('div', { className: 'auto-pagination-loader', textContent: '正在加载下一页...' }); document.body.appendChild(loader); Features.autoPagination.loaderElement = loader; // 找到列表容器和下一页链接 Features.autoPagination.findContainer(); Features.autoPagination.findNextPageLink(); // 添加滚动监听(使用节流函数) let scrollTimer = null; window.addEventListener('scroll', () => { if (scrollTimer) clearTimeout(scrollTimer); scrollTimer = setTimeout(Features.autoPagination.checkScroll, 200); }); }, // 查找内容容器 findContainer: () => { // 常见容器选择器 const selectors = ['.table', '.thread_list', '.threadlist', '#threadlist', '.content-list', '.forum_body', 'table.list tbody', '#content']; // 尝试所有选择器 for (const selector of selectors) { const container = document.querySelector(selector); if (container && container.children.length > 2) { Features.autoPagination.container = container; return; } } // 未找到,分析帖子链接 const links = document.querySelectorAll('a[href*="thread-"]'); if (links.length > 3) { // 找出最可能是列表容器的父元素 const parents = {}; links.forEach(link => { const parent = link.closest('tr, li, .thread, .item'); if (parent && parent.parentElement) { const parentPath = parent.parentElement.tagName.toLowerCase() + (parent.parentElement.id ? `#${parent.parentElement.id}` : ''); parents[parentPath] = (parents[parentPath] || 0) + 1; } }); // 选择包含最多帖子链接的容器 const bestParent = Object.entries(parents) .sort((a, b) => b[1] - a[1]) .shift(); if (bestParent && bestParent[1] > 2) { Features.autoPagination.container = document.querySelector(bestParent[0]); } } }, // 查找下一页链接 findNextPageLink: () => { const pagination = document.querySelector('.pagination'); if (!pagination) return; // 常见下一页按钮模式 const nextLinkSelectors = [ // 活动页面后面的链接 () => { const active = pagination.querySelector('.active, .current'); if (active && active.nextElementSibling) { return active.nextElementSibling.querySelector('a'); } return null; }, // 下一页文本或符号 () => { const links = pagination.querySelectorAll('a'); for (const link of links) { const text = link.textContent.trim(); if (text === '下一页' || text === '>' || text === '›' || text === '»' || text === '▶' || link.title?.includes('下一页') || link.title?.includes('Next')) { return link; } } return null; }, // 当前页数+1 () => { const active = pagination.querySelector('.active, .current'); if (active) { const currentPage = parseInt(active.textContent.trim()); if (currentPage) { const links = pagination.querySelectorAll('a'); for (const link of links) { if (parseInt(link.textContent.trim()) === currentPage + 1) { return link; } } } } return null; } ]; // 尝试所有选择器 for (const selector of nextLinkSelectors) { const link = selector(); if (link && link.href) { Features.autoPagination.nextPageLink = link; return; } } }, // 检查滚动位置和加载下一页 checkScroll: () => { if (Features.autoPagination.isLoading || !Features.autoPagination.nextPageLink) return; const scrollBottom = document.documentElement.scrollHeight - window.scrollY - window.innerHeight; if (scrollBottom < 300) { Features.autoPagination.loadNextPage(); } }, // 加载下一页内容 loadNextPage: () => { if (Features.autoPagination.isLoading || !Features.autoPagination.nextPageLink) return; // 无容器时直接跳转 if (!Features.autoPagination.container) { location.href = Features.autoPagination.nextPageLink.href; return; } Features.autoPagination.isLoading = true; // 显示加载提示 const loader = Features.autoPagination.loaderElement; if (loader) { loader.textContent = '正在加载下一页...'; loader.classList.add('show'); } // 获取下一页内容 fetch(Features.autoPagination.nextPageLink.href) .then(res => res.text()) .then(html => { const doc = new DOMParser().parseFromString(html, 'text/html'); // 尝试找到新页面中对应的内容容器 const container = Features.autoPagination.container; let selector = container.id ? `#${container.id}` : container.className ? `.${container.className.replace(/\s+/g, '.')}` : container.tagName.toLowerCase(); let newContent = doc.querySelector(selector); // 找不到时尝试其他选择器 if (!newContent) { for (const sel of ['.table', '.thread_list', '.threadlist', '#threadlist']) { newContent = doc.querySelector(sel); if (newContent) break; } } if (!newContent) throw new Error('未找到内容'); // 提取内容项 const isTable = /^(TABLE|TBODY)$/i.test(container.tagName); const items = isTable ? Array.from(newContent.querySelectorAll('tr')).filter(tr => !tr.classList.contains('header')) : Array.from(newContent.children); if (items.length < 3) throw new Error('内容项太少'); // 创建分页标记 const marker = Utils.createEl('div', { className: 'bhb-page-marker', textContent: `第 ${++Features.autoPagination.pageCount} 页` }); // 添加新内容 if (isTable) { const tbody = container.tagName === 'TABLE' ? container.querySelector('tbody') : container; const row = document.createElement('tr'); const cell = document.createElement('td'); cell.colSpan = 10; cell.appendChild(marker); row.appendChild(cell); tbody.appendChild(row); items.forEach(item => tbody.appendChild(item.cloneNode(true))); } else { container.appendChild(marker); items.forEach(item => container.appendChild(item.cloneNode(true))); } // 更新URL history.pushState( { page: Features.autoPagination.pageCount }, document.title, Features.autoPagination.nextPageLink.href ); // 处理新内容 if (GM_getValue('newtab', true)) Features.openInNewTab(container); Features.blockUser(container); // 查找新的下一页链接 const pagination = doc.querySelector('.pagination'); if (pagination) { const active = pagination.querySelector('.active, .current'); if (active && active.nextElementSibling) { const nextLink = active.nextElementSibling.querySelector('a'); if (nextLink && nextLink.href) { Features.autoPagination.nextPageLink = { href: nextLink.href, textContent: nextLink.textContent }; } else { Features.autoPagination.nextPageLink = null; } } else { Features.autoPagination.nextPageLink = null; } } else { Features.autoPagination.nextPageLink = null; } // 重置状态 Features.autoPagination.isLoading = false; if (loader) loader.classList.remove('show'); }) .catch(error => { console.error('[BHB] 加载失败:', error); if (confirm('无刷新加载失败,是否直接跳转到下一页?')) { location.href = Features.autoPagination.nextPageLink.href; } Features.autoPagination.isLoading = false; if (loader) { loader.textContent = '加载失败,点击重试'; loader.classList.add('show'); loader.onclick = Features.autoPagination.loadNextPage; setTimeout(() => loader.classList.remove('show'), 3000); } }); }, // 清理资源 cleanup: () => { window.removeEventListener('scroll', Features.autoPagination.checkScroll); if (Features.autoPagination.loaderElement) Features.autoPagination.loaderElement.remove(); } }, // 屏蔽用户 blockUser: () => { const blockedUsers = GM_getValue('blockedUsers', '') .split('\n').map(u => u.trim()).filter(Boolean); if (blockedUsers.length === 0) return; Utils.$$('.thread.card.tap, .media.post, .post_reply_item').forEach(post => { const userEl = Utils.$('.username a, .username, .reply_item_user a', post); if (userEl && blockedUsers.includes(userEl.textContent.trim())) { post.classList.add('blocked-post'); } }); }, // AI摘要 aiSummary: { // 获取AI摘要 getSummary: async (text) => { const apiKey = GM_getValue('api_key', CONFIG.defaultApiKey); return Utils.request(CONFIG.apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` }, data: { model: CONFIG.defaultModel, messages: [ { role: 'system', content: '两句话总结帖子的内容' }, { role: 'user', content: text } ], stream: false, max_tokens: 512, temperature: 0.7, top_p: 0.7, top_k: 50, frequency_penalty: 0.5, n: 1, response_format: { type: 'text' } }, isJson: true }).then(result => { if (result.error) throw new Error(result.error.message); return result.choices[0].message.content.trim(); }); }, // 添加AI摘要按钮 addButtons: () => { if (!GM_getValue('ai_summary', false) || !location.href.includes('/thread-')) return; const mainPost = Utils.$('.message.break-all'); if (!mainPost?.textContent.trim() || mainPost.classList.contains('ai-summary-added') || Utils.$('.ai-summary-btn')) return; const summaryDiv = Utils.createEl('div', { className: 'ai-summary', innerHTML: '<div class="ai-loading">正在生成AI摘要...</div>' }); const summaryBtn = Utils.createEl('button', { className: 'ai-summary-btn', textContent: 'AI总结', onclick: () => Utils.toggleClass(summaryDiv, 'hide') }); mainPost.parentNode.insertBefore(summaryDiv, mainPost); mainPost.parentNode.insertBefore(summaryBtn, summaryDiv); Features.aiSummary.getSummary(mainPost.textContent.trim()) .then(summary => { summaryDiv.innerHTML = `<div style="color:#fff">${summary}</div>`; mainPost.classList.add('ai-summary-added'); }) .catch(error => { summaryDiv.innerHTML = `<div style="color:#ff4444">生成摘要失败:${error.message}</div>`; setTimeout(() => { summaryDiv.remove(); summaryBtn.remove(); }, 3000); }); } }, // 新帖通知 notification: { timer: null, // 获取最新帖子 fetchPosts: async () => { const html = await Utils.request('https://boyshelpboys.com/'); const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); return Utils.$$('.thread', doc) .map(post => { const id = post.getAttribute('data-tid') || post.id || ''; const titleEl = Utils.$('.subject, .title, h3, a[title]', post); const authorEl = Utils.$('.username, .author, .user', post); const url = post.getAttribute('data-href') || (Utils.$('a', post)?.getAttribute('href') || ''); return { id, title: titleEl ? titleEl.textContent.trim() : '无标题', author: authorEl ? authorEl.textContent.trim() : '匿名', url }; }) .filter(p => p.id && p.title && p.url); }, // 检查新帖子 check: async () => { try { const posts = await Features.notification.fetchPosts(); const knownIds = GM_getValue('knownPostsIds', []); // 首次运行,只保存ID if (!knownIds.length) { GM_setValue('knownPostsIds', posts.map(p => p.id)); return; } // 找出新帖 const newPosts = posts.filter(p => !knownIds.includes(p.id)); // 发送通知 if (newPosts.length > 0) { // 显示最多CONFIG.maxNotifications个通知 newPosts.slice(0, CONFIG.maxNotifications).forEach(post => { Utils.notify( '论坛新帖提醒', `${post.title}\n作者: ${post.author}`, `https://boyshelpboys.com/${post.url}`, post.id ); }); // 超过限制显示汇总通知 if (newPosts.length > CONFIG.maxNotifications) { Utils.notify( '论坛新帖提醒', `还有 ${newPosts.length - CONFIG.maxNotifications} 个新帖未显示,点击查看`, 'https://boyshelpboys.com/' ); } } // 更新已知ID列表,保留最新的CONFIG.maxSavedIds个 const mergedIds = [...new Set([...posts.map(p => p.id), ...knownIds])].slice(0, CONFIG.maxSavedIds); GM_setValue('knownPostsIds', mergedIds); } catch (error) { console.error('检查新帖失败', error); } }, // 设置通知 setup: () => { if (Features.notification.timer) { clearInterval(Features.notification.timer); Features.notification.timer = null; } if (GM_getValue('notification', false)) { Features.notification.check(); Features.notification.timer = setInterval( Features.notification.check, CONFIG.checkInterval ); } } }, // 快速预览功能 quickPreview: { previewContainer: null, previewTimeout: null, previewDelay: 500, // 悬停多久后显示预览 cache: {}, // 缓存已加载的帖子内容 currentLink: null, // 当前预览的链接元素 // 创建预览容器 createContainer: () => { if (Features.quickPreview.previewContainer) return; const container = Utils.createEl('div', { className: 'bhb-preview-container', innerHTML: ` <div class="bhb-preview-header"> <div class="bhb-preview-title">加载中...</div> <div class="bhb-preview-info"></div> </div> <div class="bhb-preview-content"> <div class="bhb-preview-loading">加载中...</div> </div> <div class="bhb-preview-footer"> <span>点击链接查看完整内容</span> <span>社区助手提供</span> </div> ` }); document.body.appendChild(container); Features.quickPreview.previewContainer = container; // 添加点击事件监听器到document document.addEventListener('click', (e) => { // 如果点击的是预览容器内部或当前预览的链接,不关闭 if (container.contains(e.target) || (Features.quickPreview.currentLink && Features.quickPreview.currentLink.contains(e.target))) { return; } // 点击其他地方,关闭预览 if (container.classList.contains('show')) { container.classList.remove('show'); Features.quickPreview.currentLink = null; // 移除所有高亮 Utils.$$('.bhb-preview-highlight').forEach(el => { el.classList.remove('bhb-preview-highlight'); }); } }); // 添加键盘事件监听器 - ESC键关闭预览 document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && container.classList.contains('show')) { container.classList.remove('show'); Features.quickPreview.currentLink = null; // 移除所有高亮 Utils.$$('.bhb-preview-highlight').forEach(el => { el.classList.remove('bhb-preview-highlight'); }); } }); }, // 获取帖子内容 fetchPostContent: async (url) => { // 如果已缓存,直接返回 if (Features.quickPreview.cache[url]) { return Features.quickPreview.cache[url]; } try { const html = await Utils.request(url); const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); // 尝试找到主帖内容 const mainPost = doc.querySelector('.message.break-all, .post-content, .thread-content'); if (!mainPost) throw new Error('无法找到帖子内容'); // 获取帖子标题 const title = doc.querySelector('h1, .subject, .thread-subject')?.textContent.trim() || '无标题'; // 获取作者和时间 const author = doc.querySelector('.username, .author')?.textContent.trim() || '匿名'; const time = doc.querySelector('.date, .time, .post-time')?.textContent.trim() || ''; // 处理内容中的图片 const content = mainPost.innerHTML; const processedContent = content .replace(/<img[^>]*src="([^"]+)"[^>]*>/gi, '<img class="bhb-preview-img" src="$1" alt="图片">') .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, ''); // 移除脚本标签 const result = { title, author, time, content: processedContent }; // 缓存结果 Features.quickPreview.cache[url] = result; return result; } catch (error) { console.error('获取帖子内容失败', error); throw error; } }, // 显示预览 showPreview: (link, event) => { if (!GM_getValue('quick_preview', false)) return; Features.quickPreview.createContainer(); const container = Features.quickPreview.previewContainer; // 移除之前的高亮 if (Features.quickPreview.currentLink && Features.quickPreview.currentLink !== link) { const parentElement = Features.quickPreview.currentLink.closest('tr, li, .thread, .item'); if (parentElement) { parentElement.classList.remove('bhb-preview-highlight'); } } // 保存当前预览的链接 Features.quickPreview.currentLink = link; // 高亮当前链接所在的行或项目 const parentElement = link.closest('tr, li, .thread, .item'); if (parentElement) { parentElement.classList.add('bhb-preview-highlight'); } // 清除之前的定时器 if (Features.quickPreview.previewTimeout) { clearTimeout(Features.quickPreview.previewTimeout); } // 设置新的定时器 Features.quickPreview.previewTimeout = setTimeout(async () => { try { // 显示加载状态 container.querySelector('.bhb-preview-title').textContent = '加载中...'; container.querySelector('.bhb-preview-info').textContent = ''; container.querySelector('.bhb-preview-content').innerHTML = '<div class="bhb-preview-loading">加载中...</div>'; container.classList.add('show'); // 获取帖子内容 const url = link.href; const postData = await Features.quickPreview.fetchPostContent(url); // 更新预览内容 container.querySelector('.bhb-preview-title').textContent = postData.title; container.querySelector('.bhb-preview-info').textContent = `${postData.author} ${postData.time}`; container.querySelector('.bhb-preview-content').innerHTML = postData.content; } catch (error) { container.querySelector('.bhb-preview-content').innerHTML = `<div class="bhb-preview-error">加载失败: ${error.message}</div>`; } }, Features.quickPreview.previewDelay); }, // 添加预览事件 addPreviewEvents: () => { if (!GM_getValue('quick_preview', false)) return; // 针对论坛特定结构的头像选择器 const avatarSelectors = [ '.v_avatar', // 头像容器 '.v_avatar img', // 头像图片 '.avatar-3', // 具体的头像类名 'img.avatar' // 通用头像 ]; // 为所有头像添加预览事件 Utils.$$(avatarSelectors.join(', ')).forEach(element => { if (element.classList.contains('bhb-preview-added')) return; // 标记已添加预览事件 element.classList.add('bhb-preview-added'); // 查找关联的帖子容器 let container = element.closest('li.thread, .thread'); if (!container) return; // 查找帖子链接或使用容器的data-href属性 let link = container.querySelector('a.subject[href*="thread-"]'); // 如果找不到链接,尝试从data-href属性获取 if (!link && container.hasAttribute('data-href')) { // 创建一个虚拟链接对象 link = { href: 'https://boyshelpboys.com/' + container.getAttribute('data-href'), textContent: container.querySelector('.subject')?.textContent || '帖子预览' }; } // 如果找不到任何链接,放弃 if (!link) return; // 添加鼠标悬停事件 element.style.cursor = 'pointer'; element.setAttribute('title', '悬停查看帖子预览'); element.addEventListener('mouseenter', (e) => { Features.quickPreview.showPreview(link, e); }); }); // 备选方案:直接处理thread元素 if (Utils.$$('.bhb-preview-added').length === 0) { Utils.$$('li.thread[data-href*="thread-"]').forEach(thread => { const avatarContainer = thread.querySelector('.v_avatar'); if (!avatarContainer || avatarContainer.classList.contains('bhb-preview-added')) return; avatarContainer.classList.add('bhb-preview-added'); avatarContainer.style.cursor = 'pointer'; avatarContainer.setAttribute('title', '悬停查看帖子预览'); const link = { href: 'https://boyshelpboys.com/' + thread.getAttribute('data-href'), textContent: thread.querySelector('.subject')?.textContent || '帖子预览' }; avatarContainer.addEventListener('mouseenter', (e) => { Features.quickPreview.showPreview(link, e); }); }); } }, // 清理资源 cleanup: () => { if (Features.quickPreview.previewContainer) { Features.quickPreview.previewContainer.remove(); Features.quickPreview.previewContainer = null; } if (Features.quickPreview.previewTimeout) { clearTimeout(Features.quickPreview.previewTimeout); Features.quickPreview.previewTimeout = null; } // 移除所有高亮 Utils.$$('.bhb-preview-highlight').forEach(el => { el.classList.remove('bhb-preview-highlight'); }); Features.quickPreview.cache = {}; Features.quickPreview.currentLink = null; } }, // 控制面板 panel: { create: () => { const panel = Utils.createEl('div', { id: 'bhb-panel', innerHTML: ` <div id="bhb-content"> <div id="bhb-panel-header"> <span id="bhb-panel-title">社区助手</span> <span class="bhb-tip">点击收起</span> </div> <div class="bhb-section"> <div class="bhb-section-title">功能开关</div> <div class="bhb-options-grid"> <div class="bhb-option"> <label class="bhb-checkbox-label"> <input type="checkbox" id="bhb-newtab" class="bhb-checkbox"> <span class="bhb-label">新标签</span> </label> </div> <div class="bhb-option"> <label class="bhb-checkbox-label"> <input type="checkbox" id="bhb-notification" class="bhb-checkbox"> <span class="bhb-label">新帖通知</span> </label> </div> <div class="bhb-option"> <label class="bhb-checkbox-label"> <input type="checkbox" id="bhb-auto-pagination" class="bhb-checkbox"> <span class="bhb-label">自动翻页</span> </label> </div> <div class="bhb-option"> <label class="bhb-checkbox-label"> <input type="checkbox" id="bhb-ai-summary" class="bhb-checkbox"> <span class="bhb-label">AI总结</span> </label> </div> <div class="bhb-option"> <label class="bhb-checkbox-label"> <input type="checkbox" id="bhb-quick-preview" class="bhb-checkbox"> <span class="bhb-label">快速预览</span> </label> </div> </div> </div> <div class="bhb-section"> <div class="bhb-section-title">高级设置</div> <div class="bhb-input-section"> <div class="bhb-option"> <span class="bhb-btn" id="bhb-toggle-block">屏蔽用户</span> </div> <div class="bhb-option"> <span class="bhb-btn" id="bhb-toggle-css">自定义CSS</span> </div> </div> <div class="bhb-input-area" id="bhb-block-area"> <textarea class="bhb-textarea" id="bhb-blocked-users" placeholder="每行一个用户名">${GM_getValue('blockedUsers', '')}</textarea> <div style="display:flex;justify-content:flex-end"> <button class="bhb-btn" id="bhb-apply-block" style="width:auto">应用</button> </div> </div> <div class="bhb-input-area" id="bhb-css-area"> <textarea class="bhb-textarea" id="bhb-css" placeholder="输入CSS代码">${GM_getValue('customCSS', '')}</textarea> <div style="display:flex;justify-content:flex-end"> <button class="bhb-btn" id="bhb-apply-css" style="width:auto">应用</button> </div> </div> </div> </div> <div id="bhb-toggle">B</div> ` }); document.body.appendChild(panel); // 创建帮助区域 const helpArea = Utils.createEl('div', { id: 'bhb-help-area', className: 'bhb-input-area', innerHTML: ` <div class="bhb-help-content"> <h4 style="margin:4px 0;color:#ddd">BOYS HELP BOYS</h4> <p style="margin:6px 0;line-height:1.4;font-size:12px"> - 鼠标悬浮到帖子头像即可预览帖子内容<br> <br> 使用过程中有任何问题,请联系开发者。 </p> </div> ` }); panel.appendChild(helpArea); Features.panel.setup(panel); }, setup: (panel) => { // 添加自定义样式元素 const customStyle = Utils.createEl('style', { id: 'bhb-custom-style', textContent: GM_getValue('customCSS', '') }); document.head.appendChild(customStyle); // 切换输入区域 ['block', 'css'].forEach(type => { Utils.$(`#bhb-toggle-${type}`, panel).onclick = () => { const area = Utils.$(`#bhb-${type}-area`, panel); Utils.$$('.bhb-input-area', panel).forEach(a => a !== area && a.classList.remove('active')); Utils.toggleClass(area, 'active'); }; }); // AI总结设置 const aiSummaryCheckbox = Utils.$('#bhb-ai-summary', panel); aiSummaryCheckbox.checked = GM_getValue('ai_summary', false); aiSummaryCheckbox.onchange = () => { GM_setValue('ai_summary', aiSummaryCheckbox.checked); if (aiSummaryCheckbox.checked) { Features.aiSummary.addButtons(); } else { Utils.$$('.ai-summary, .ai-summary-btn').forEach(el => el.remove()); Utils.$$('.ai-summary-added').forEach(el => el.classList.remove('ai-summary-added')); } }; // 面板最小化功能 const togglePanel = () => { Utils.toggleClass(panel, 'mini'); GM_setValue('panelMini', panel.classList.contains('mini')); }; Utils.$('#bhb-panel-header', panel).onclick = togglePanel; // 修改B按钮功能为展开帮助说明 Utils.$('#bhb-toggle', panel).onclick = () => { if (panel.classList.contains('mini')) { // 如果面板是最小化状态,先恢复面板 Utils.toggleClass(panel, 'mini'); GM_setValue('panelMini', false); } else { // 否则切换帮助区域显示/隐藏 const helpArea = Utils.$('#bhb-help-area', panel); Utils.toggleClass(helpArea, 'active'); } }; if (GM_getValue('panelMini', false)) panel.classList.add('mini'); // 新标签页设置 const newTabCheckbox = Utils.$('#bhb-newtab', panel); newTabCheckbox.checked = GM_getValue('newtab', true); newTabCheckbox.onchange = () => { GM_setValue('newtab', newTabCheckbox.checked); newTabCheckbox.checked && Features.openInNewTab(); }; // 新帖通知设置 const notificationCheckbox = Utils.$('#bhb-notification', panel); notificationCheckbox.checked = GM_getValue('notification', false); notificationCheckbox.onchange = async () => { const checked = notificationCheckbox.checked; if (checked && "Notification" in window) { const permission = await Notification.requestPermission(); if (permission !== 'granted') { alert('需要开启浏览器通知权限才能接收新帖通知!'); notificationCheckbox.checked = false; return; } } GM_setValue('notification', checked); Features.notification.setup(); }; // 自动翻页设置 const autoPaginationCheckbox = Utils.$('#bhb-auto-pagination', panel); autoPaginationCheckbox.checked = GM_getValue('auto_pagination', false); autoPaginationCheckbox.onchange = () => { const checked = autoPaginationCheckbox.checked; GM_setValue('auto_pagination', checked); if (checked) { Features.autoPagination.init(); } else { Features.autoPagination.cleanup(); } }; // 屏蔽用户设置 Utils.$('#bhb-apply-block', panel).onclick = () => { GM_setValue('blockedUsers', Utils.$('#bhb-blocked-users', panel).value.trim()); Features.blockUser(); }; // 自定义CSS设置 Utils.$('#bhb-apply-css', panel).onclick = () => { const css = Utils.$('#bhb-css', panel).value.trim(); GM_setValue('customCSS', css); customStyle.textContent = css; }; // 快速预览设置 const quickPreviewCheckbox = Utils.$('#bhb-quick-preview', panel); quickPreviewCheckbox.checked = GM_getValue('quick_preview', false); quickPreviewCheckbox.onchange = () => { GM_setValue('quick_preview', quickPreviewCheckbox.checked); if (quickPreviewCheckbox.checked) { Features.quickPreview.addPreviewEvents(); } else { Features.quickPreview.cleanup(); } }; } } }; // 初始化函数 const init = () => { Features.panel.create(); if (GM_getValue('newtab', true)) Features.openInNewTab(); Features.blockUser(); Features.notification.setup(); Features.autoPagination.init(); if (GM_getValue('quick_preview', false)) Features.quickPreview.addPreviewEvents(); // 动态内容监听 const observer = new MutationObserver((() => { let timer = null; return () => { if (timer) return; timer = setTimeout(() => { if (GM_getValue('newtab', true)) Features.openInNewTab(); Features.blockUser(); if (GM_getValue('ai_summary', false)) Features.aiSummary.addButtons(); if (GM_getValue('quick_preview', false)) Features.quickPreview.addPreviewEvents(); timer = null; }, 1000); }; })()); if (document.body) { observer.observe(document.body, { childList: true, subtree: true }); if (GM_getValue('ai_summary', false)) Features.aiSummary.addButtons(); } }; // 等待DOM加载完成后初始化 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();