// ==UserScript== // @name HDSky 体育沙龙面板 // @namespace http://tampermonkey.net/ // @version 2.7 // @description 在 HDSky 论坛页面右上角显示控制面板,自动高亮特殊关注用户的回复内容,支持快速翻页和收藏功能,可折叠面板,下拉加载翻页 // @author 江畔 // @match https://hdsky.me/* // @match https://www.hdsky.me/* // @icon https://hdsky.me/favicon.ico // @grant GM_setValue // @grant GM_getValue // @charset UTF-8 // @license MIT // @downloadURL none // ==/UserScript== (function() { 'use strict'; // 配置管理 const Config = { // 获取配置 get(key, defaultValue) { return GM_getValue(key, defaultValue); }, // 设置配置 set(key, value) { GM_setValue(key, value); }, // 获取下拉加载开关状态 getAutoLoadEnabled() { return this.get('autoLoadEnabled', false); }, // 设置下拉加载开关状态 setAutoLoadEnabled(enabled) { this.set('autoLoadEnabled', enabled); }, // 获取面板展开状态 getPanelExpanded() { return this.get('panelExpanded', true); // 默认展开 }, // 设置面板展开状态 setPanelExpanded(expanded) { this.set('panelExpanded', expanded); } }; // 从存储中获取特殊关注名单 function getSpecialFollowList() { const listStr = Config.get('specialFollowList', ''); if (!listStr) return []; return listStr.split(',').map(name => name.trim()).filter(name => name); } // 保存特殊关注名单到存储 function saveSpecialFollowList(list) { Config.set('specialFollowList', list.join(',')); } // 从存储中获取收藏列表 function getBookmarkList() { const listStr = Config.get('bookmarkList', '[]'); try { return JSON.parse(listStr); } catch (e) { return []; } } // 保存收藏列表到存储 function saveBookmarkList(list) { Config.set('bookmarkList', JSON.stringify(list)); } // 创建控制面板 function createControlPanel() { // 创建容器,包含面板和折叠按钮 const container = document.createElement('div'); container.id = 'hdsky-panel-container'; container.style.cssText = ` position: fixed; top: 10px; right: 10px; display: flex; align-items: flex-start; z-index: 10000; `; // 创建折叠按钮 const toggleBtn = document.createElement('button'); toggleBtn.id = 'panel-toggle-btn'; toggleBtn.innerHTML = '◀'; toggleBtn.title = '收起面板'; toggleBtn.style.cssText = ` background: #e0e0e0; color: #666; border: none; border-radius: 4px 0 0 4px; width: 24px; height: 40px; cursor: pointer; font-size: 14px; transition: background 0.3s; margin-right: -2px; z-index: 1; `; toggleBtn.onmouseover = () => toggleBtn.style.background = '#d0d0d0'; toggleBtn.onmouseout = () => toggleBtn.style.background = '#e0e0e0'; toggleBtn.onclick = togglePanel; const panel = document.createElement('div'); panel.id = 'hdsky-special-follow-panel'; panel.style.cssText = ` background: #fff; border: 1px solid #ddd; border-radius: 8px; padding: 15px; box-shadow: 0 4px 8px rgba(0,0,0,0.2); width: 220px; font-family: Arial, sans-serif; transition: all 0.3s ease; `; // 面板标题 const title = document.createElement('div'); title.textContent = '体育沙龙面板'; title.style.cssText = ` font-size: 16px; font-weight: bold; margin-bottom: 12px; color: #333; text-align: center; border-bottom: 1px solid #ddd; padding-bottom: 8px; `; panel.appendChild(title); // 关注列表按钮 const followListBtn = document.createElement('button'); followListBtn.id = 'follow-list-btn'; followListBtn.textContent = '关注列表'; followListBtn.style.cssText = ` padding: 10px 15px; background: #2196F3; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 14px; font-weight: bold; transition: background 0.3s; width: 100%; margin-bottom: 10px; `; followListBtn.title = '点击编辑特殊关注名单'; followListBtn.onmouseover = () => followListBtn.style.background = '#0b7dda'; followListBtn.onmouseout = () => followListBtn.style.background = '#2196F3'; followListBtn.onclick = handleFollowListClick; panel.appendChild(followListBtn); // 按钮容器 const buttonContainer = document.createElement('div'); buttonContainer.style.cssText = ` display: flex; flex-direction: column; gap: 8px; `; // 下拉加载翻页按钮 const autoLoadBtn = document.createElement('button'); autoLoadBtn.id = 'auto-load-btn'; autoLoadBtn.style.cssText = ` padding: 10px 15px; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 14px; font-weight: bold; transition: background 0.3s; width: 100%; margin-bottom: 10px; `; autoLoadBtn.onclick = toggleAutoLoadFromPanel; updateAutoLoadButton(autoLoadBtn); // 在设置基础样式后再更新按钮状态 buttonContainer.appendChild(autoLoadBtn); // 收藏功能按钮容器 const bookmarkContainer = document.createElement('div'); bookmarkContainer.id = 'bookmark-container'; bookmarkContainer.style.cssText = ` display: flex; flex-direction: row; gap: 8px; `; // 收藏按钮 const bookmarkBtn = document.createElement('button'); bookmarkBtn.textContent = '收藏'; bookmarkBtn.id = 'bookmark-btn'; bookmarkBtn.style.cssText = ` padding: 8px 12px; background: #2196F3; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 13px; font-weight: bold; transition: background 0.3s; flex: 1; `; bookmarkBtn.onmouseover = () => bookmarkBtn.style.background = '#0b7dda'; bookmarkBtn.onmouseout = () => bookmarkBtn.style.background = '#2196F3'; bookmarkBtn.onclick = addBookmark; bookmarkContainer.appendChild(bookmarkBtn); // 收藏夹按钮 const bookmarkListBtn = document.createElement('button'); bookmarkListBtn.textContent = '收藏夹'; bookmarkListBtn.id = 'bookmark-list-btn'; bookmarkListBtn.style.cssText = ` padding: 8px 12px; background: #2196F3; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 13px; font-weight: bold; transition: background 0.3s; flex: 1; `; bookmarkListBtn.onmouseover = () => bookmarkListBtn.style.background = '#0b7dda'; bookmarkListBtn.onmouseout = () => bookmarkListBtn.style.background = '#2196F3'; bookmarkListBtn.onclick = showBookmarkList; bookmarkContainer.appendChild(bookmarkListBtn); buttonContainer.appendChild(bookmarkContainer); panel.appendChild(buttonContainer); // 将按钮和面板添加到容器 container.appendChild(toggleBtn); container.appendChild(panel); // 添加到页面 document.body.appendChild(container); // 应用保存的面板状态 const isPanelExpanded = Config.getPanelExpanded(); if (!isPanelExpanded) { // 如果保存的是收起状态,则收起面板 panel.style.display = 'none'; toggleBtn.innerHTML = '▶'; toggleBtn.title = '展开面板'; toggleBtn.style.borderRadius = '4px'; } } // 切换面板显示/隐藏 function togglePanel() { const panel = document.getElementById('hdsky-special-follow-panel'); const toggleBtn = document.getElementById('panel-toggle-btn'); if (panel.style.display === 'none') { // 展开面板 panel.style.display = 'block'; toggleBtn.innerHTML = '◀'; toggleBtn.title = '收起面板'; toggleBtn.style.borderRadius = '4px 0 0 4px'; Config.setPanelExpanded(true); // 保存展开状态 } else { // 收起面板 panel.style.display = 'none'; toggleBtn.innerHTML = '▶'; toggleBtn.title = '展开面板'; toggleBtn.style.borderRadius = '4px'; Config.setPanelExpanded(false); // 保存收起状态 } } // 更新下拉加载按钮的显示 function updateAutoLoadButton(button) { const isEnabled = Config.getAutoLoadEnabled(); button.style.background = '#2196F3'; // 统一使用蓝色 button.onmouseover = () => button.style.background = '#0b7dda'; button.onmouseout = () => button.style.background = '#2196F3'; if (isEnabled) { button.textContent = '✅ 下拉加载翻页'; button.title = '点击关闭下拉加载翻页功能'; } else { button.textContent = '❌ 下拉加载翻页'; button.title = '点击开启下拉加载翻页功能'; } } // 从面板切换下拉加载功能 function toggleAutoLoadFromPanel() { const currentState = Config.getAutoLoadEnabled(); const newState = !currentState; Config.setAutoLoadEnabled(newState); // 更新按钮显示 const button = document.getElementById('auto-load-btn'); if (button) { updateAutoLoadButton(button); } // 显示提示 const message = (newState ? '已开启' : '已关闭') + '下拉加载翻页!'; showToast(message, newState ? 'success' : 'info'); // 如果是开启,立即初始化功能 if (newState) { autoLoadNextPage(); } else { // 如果是关闭,需要刷新页面以移除事件监听器 if (confirm('需要刷新页面以完全关闭下拉加载功能,是否立即刷新?')) { location.reload(); } } } // 显示消息提示 function showToast(message, type = 'info') { const toast = document.createElement('div'); toast.style.cssText = ` position: fixed; top: 80px; right: 20px; background: ${type === 'success' ? 'rgba(76, 175, 80, 0.95)' : type === 'error' ? 'rgba(244, 67, 54, 0.95)' : 'rgba(33, 150, 243, 0.95)'}; color: white; padding: 12px 20px; border-radius: 5px; box-shadow: 0 4px 12px rgba(0,0,0,0.3); z-index: 10001; font-size: 14px; font-weight: bold; animation: slideInRight 0.3s ease-out; `; toast.textContent = message; // 添加动画样式 const style = document.createElement('style'); style.textContent = ` @keyframes slideInRight { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } `; document.head.appendChild(style); document.body.appendChild(toast); setTimeout(() => { toast.style.transition = 'all 0.3s ease-out'; toast.style.transform = 'translateX(120%)'; toast.style.opacity = '0'; setTimeout(() => toast.remove(), 300); }, 2500); } // 全局变量:高亮帖子列表 let highlightedPosts = []; // 添加收藏 function addBookmark() { const currentUrl = window.location.href; let currentTitle = document.title || '未命名页面'; // 从标题中提取引号里的内容 const match = currentTitle.match(/"([^"]+)"/); if (match && match[1]) { currentTitle = match[1]; } else { // 如果没有引号,则删掉常见的前后缀 currentTitle = currentTitle.replace(/^HDSky :: 查看主题\s+/i, ''); currentTitle = currentTitle.replace(/^HDSky :: /i, ''); currentTitle = currentTitle.replace(/\s*高清视界.*$/i, ''); currentTitle = currentTitle.replace(/\s*-\s*Powered by.*$/i, ''); } // 获取现有收藏列表 const bookmarks = getBookmarkList(); // 检查是否已经收藏 const exists = bookmarks.some(b => b.url === currentUrl); if (exists) { alert('该页面已经在收藏夹中了!'); return; } // 添加新收藏 bookmarks.push({ url: currentUrl, title: currentTitle, time: new Date().toLocaleString() }); // 保存 saveBookmarkList(bookmarks); alert('收藏成功!\n标题:' + currentTitle); } // 显示收藏夹 function showBookmarkList() { const bookmarks = getBookmarkList(); // 移除旧的收藏夹窗口(如果存在) const oldDialog = document.getElementById('bookmark-dialog'); if (oldDialog) { oldDialog.remove(); } // 创建遮罩层 const overlay = document.createElement('div'); overlay.id = 'bookmark-dialog'; overlay.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); z-index: 10003; display: flex; justify-content: center; align-items: center; `; // 创建弹窗 const dialog = document.createElement('div'); dialog.style.cssText = ` background: white; border-radius: 10px; padding: 20px; width: 600px; max-height: 70vh; overflow-y: auto; box-shadow: 0 4px 20px rgba(0,0,0,0.3); `; // 标题栏 const header = document.createElement('div'); header.style.cssText = ` display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 2px solid #2196F3; background: transparent; `; const title = document.createElement('h2'); title.textContent = '我的收藏夹'; title.style.cssText = ` margin: 0; padding: 0; color: #2196F3; font-size: 20px; background: transparent; border: none; outline: none; `; header.appendChild(title); const closeBtn = document.createElement('button'); closeBtn.textContent = '✕'; closeBtn.style.cssText = ` background: #f44336; color: white; border: none; border-radius: 50%; width: 30px; height: 30px; cursor: pointer; font-size: 18px; font-weight: bold; transition: background 0.3s; `; closeBtn.onmouseover = () => closeBtn.style.background = '#d32f2f'; closeBtn.onmouseout = () => closeBtn.style.background = '#f44336'; closeBtn.onclick = () => overlay.remove(); header.appendChild(closeBtn); dialog.appendChild(header); // 收藏列表 if (bookmarks.length === 0) { const emptyMsg = document.createElement('div'); emptyMsg.textContent = '收藏夹还是空的,快去收藏喜欢的页面吧!'; emptyMsg.style.cssText = ` text-align: center; color: #999; padding: 40px 20px; font-size: 14px; background: #f9f9f9; border-radius: 5px; margin-top: 10px; `; dialog.appendChild(emptyMsg); } else { bookmarks.forEach((bookmark, index) => { const item = createBookmarkItem(bookmark, index); dialog.appendChild(item); }); } overlay.appendChild(dialog); document.body.appendChild(overlay); // 点击遮罩层关闭 overlay.onclick = (e) => { if (e.target === overlay) { overlay.remove(); } }; } // 创建收藏项 function createBookmarkItem(bookmark, index) { const item = document.createElement('div'); item.style.cssText = ` display: flex; justify-content: space-between; align-items: center; padding: 12px; margin-bottom: 10px; background: #fafafa; border: 1px solid #e0e0e0; border-radius: 5px; transition: all 0.3s; `; item.onmouseover = () => { item.style.background = '#e3f2fd'; item.style.borderColor = '#2196F3'; item.style.transform = 'translateX(5px)'; item.style.boxShadow = '0 2px 8px rgba(33, 150, 243, 0.2)'; }; item.onmouseout = () => { item.style.background = '#fafafa'; item.style.borderColor = '#e0e0e0'; item.style.transform = 'translateX(0)'; item.style.boxShadow = 'none'; }; // 左侧内容区 const content = document.createElement('div'); content.style.cssText = ` flex: 1; cursor: pointer; overflow: hidden; `; content.onclick = () => window.location.href = bookmark.url; const titleDiv = document.createElement('div'); titleDiv.textContent = bookmark.title; titleDiv.style.cssText = ` font-size: 14px; font-weight: bold; color: #2196F3; margin-bottom: 5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; `; content.appendChild(titleDiv); const timeDiv = document.createElement('div'); timeDiv.textContent = '收藏时间: ' + bookmark.time; timeDiv.style.cssText = ` font-size: 12px; color: #999; `; content.appendChild(timeDiv); item.appendChild(content); // 删除按钮 const deleteBtn = document.createElement('button'); deleteBtn.textContent = '✕'; deleteBtn.style.cssText = ` background: #ff5722; color: white; border: none; border-radius: 50%; width: 25px; height: 25px; cursor: pointer; font-size: 14px; font-weight: bold; transition: background 0.3s; margin-left: 10px; `; deleteBtn.onmouseover = () => deleteBtn.style.background = '#e64a19'; deleteBtn.onmouseout = () => deleteBtn.style.background = '#ff5722'; deleteBtn.onclick = (e) => { e.stopPropagation(); if (confirm('确定要删除这个收藏吗?\n' + bookmark.title)) { deleteBookmark(index); showBookmarkList(); // 刷新列表 } }; item.appendChild(deleteBtn); return item; } // 删除收藏 function deleteBookmark(index) { const bookmarks = getBookmarkList(); bookmarks.splice(index, 1); saveBookmarkList(bookmarks); } // 处理关注列表点击(编辑) function handleFollowListClick() { const currentList = getSpecialFollowList(); const currentStr = currentList.join(','); const input = prompt('请输入特殊关注名单(用逗号分隔):\n例如: DFBCOLD19,李知恩', currentStr); if (input !== null) { // 用户点击了确定(包括空字符串) const newList = input.split(',').map(name => name.trim()).filter(name => name); saveSpecialFollowList(newList); alert('特殊关注名单已更新!\n当前关注: ' + (newList.length > 0 ? newList.join(', ') : '无')); // 重新应用高亮 autoHighlightFollowedPosts(); } } // 自动高亮特殊关注用户的帖子(页面加载时调用) function autoHighlightFollowedPosts() { const followList = getSpecialFollowList(); // 如果没有关注名单,不执行高亮 if (followList.length === 0) { return; } // 先清除之前的高亮 clearHighlights(); // 找到所有回复帖子(每个帖子在一个带有 margin-top 和 margin-bottom 的 div 中) const allPosts = document.querySelectorAll('div[style*="margin-top: 8pt"]'); // 重置高亮列表 highlightedPosts = []; allPosts.forEach(post => { // 在帖子中查找用户名链接 const userLinks = post.querySelectorAll('a[href*="userdetails.php"]'); let isFollowedUser = false; userLinks.forEach(link => { const username = link.textContent.trim(); // 检查是否在关注列表中 if (followList.some(followName => username === followName)) { isFollowedUser = true; } }); if (isFollowedUser) { // 高亮显示关注用户的信息div post.style.background = '#fffacd'; post.style.border = '2px solid #ffd700'; post.style.borderRadius = '5px'; post.style.padding = '5px'; // 找到并高亮div内部的table const innerTables = post.querySelectorAll('table'); innerTables.forEach(table => { table.style.background = '#fff8dc'; table.style.border = '2px solid #ffb700'; }); // 标记为已高亮 post.dataset.highlighted = 'true'; // 找到并高亮紧跟在div后面的回复内容table(class="main") let nextElement = post.nextElementSibling; if (nextElement && nextElement.tagName === 'TABLE' && nextElement.classList.contains('main')) { nextElement.style.background = '#fff8dc'; nextElement.style.border = '2px solid #ffb700'; nextElement.style.borderRadius = '5px'; // 标记这个table也被高亮了 nextElement.dataset.highlightedContent = 'true'; } // 添加到高亮列表 highlightedPosts.push(post); } }); } // 清除所有高亮 function clearHighlights() { // 找到所有被高亮的帖子div const posts = document.querySelectorAll('div[data-highlighted="true"]'); posts.forEach(post => { // 清除div的高亮样式 post.style.background = ''; post.style.border = ''; post.style.borderRadius = ''; post.style.padding = ''; post.removeAttribute('data-highlighted'); // 清除div内部table的高亮样式 const tables = post.querySelectorAll('table'); tables.forEach(table => { table.style.background = ''; table.style.border = ''; }); }); // 清除所有被高亮的回复内容table const contentTables = document.querySelectorAll('table[data-highlighted-content="true"]'); contentTables.forEach(table => { table.style.background = ''; table.style.border = ''; table.style.borderRadius = ''; table.removeAttribute('data-highlighted-content'); }); // 重置全局变量 highlightedPosts = []; } // 自动加载下一页功能 let isLoadingNextPage = false; const scrollThreshold = 800; // 距离底部多少像素时触发加载 function autoLoadNextPage() { if (!Config.getAutoLoadEnabled()) { return; } let lastScrollTop = 0; window.addEventListener('scroll', function() { const scrollTop = document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop; const scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight; const clientHeight = document.documentElement.clientHeight || window.innerHeight; // 只在向下滚动时触发 if (scrollTop > lastScrollTop) { // 判断是否接近底部 if (scrollHeight <= clientHeight + scrollTop + scrollThreshold && !isLoadingNextPage) { // 查找下一页链接 const nextLinks = document.querySelectorAll('a'); let nextPageLink = null; for (let link of nextLinks) { const text = link.textContent.trim(); if (text.includes('下一页') || text === '下一页 >>') { nextPageLink = link; break; } } if (nextPageLink && nextPageLink.href) { loadNextPage(nextPageLink.href); } } } lastScrollTop = scrollTop; }, false); } function loadNextPage(url) { isLoadingNextPage = true; // 显示加载提示 const loadingDiv = document.createElement('div'); loadingDiv.id = 'auto-loading-indicator'; loadingDiv.style.cssText = ` position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); background: rgba(33, 150, 243, 0.9); color: white; padding: 12px 24px; border-radius: 25px; box-shadow: 0 4px 12px rgba(0,0,0,0.3); z-index: 9999; font-size: 14px; font-weight: bold; `; loadingDiv.textContent = '正在加载下一页...'; document.body.appendChild(loadingDiv); // 使用 fetch 加载下一页 fetch(url) .then(response => response.text()) .then(html => { const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); // 查找主要内容区域 - 找到所有回复帖子 const newPosts = doc.querySelectorAll('div[style*="margin-top: 8pt"]'); // 找到当前页面的最后一个帖子 const currentPosts = document.querySelectorAll('div[style*="margin-top: 8pt"]'); if (currentPosts.length > 0 && newPosts.length > 0) { const lastPost = currentPosts[currentPosts.length - 1]; // 找到最后一个帖子的下一个 table(回复内容) let lastTable = lastPost.nextElementSibling; while (lastTable && lastTable.tagName !== 'TABLE') { lastTable = lastTable.nextElementSibling; } // 确定插入位置:如果有 table 就在 table 后面,否则在 div 后面 let insertAfter = lastTable || lastPost; // 将新帖子插入到页面中 newPosts.forEach(post => { // 克隆 div(用户信息) const clonedPost = post.cloneNode(true); insertAfter.parentNode.insertBefore(clonedPost, insertAfter.nextSibling); insertAfter = clonedPost; // 查找并克隆紧跟的 table(回复内容) const nextTable = post.nextElementSibling; if (nextTable && nextTable.tagName === 'TABLE' && nextTable.classList.contains('main')) { const clonedTable = nextTable.cloneNode(true); insertAfter.parentNode.insertBefore(clonedTable, insertAfter.nextSibling); insertAfter = clonedTable; } }); // 更新页码导航(找到所有

中包含"上一页"和"下一页"的元素) const newPagers = doc.querySelectorAll('p[align="center"]'); const currentPagers = document.querySelectorAll('p[align="center"]'); // 遍历并更新所有分页器 let pagerUpdateCount = 0; for (let i = 0; i < currentPagers.length && i < newPagers.length; i++) { const currentPager = currentPagers[i]; const newPager = newPagers[i]; // 检查是否包含分页链接(包含"上一页"或"下一页") if (currentPager.innerHTML.includes('上一页') || currentPager.innerHTML.includes('下一页')) { currentPager.innerHTML = newPager.innerHTML; pagerUpdateCount++; } } console.log(`已更新 ${pagerUpdateCount} 个分页导航`); // 重新应用高亮 autoHighlightFollowedPosts(); loadingDiv.textContent = '✓ 加载完成'; loadingDiv.style.background = 'rgba(76, 175, 80, 0.9)'; setTimeout(() => { loadingDiv.remove(); }, 2000); // 更新 URL(不刷新页面) history.pushState(null, '', url); } else { loadingDiv.textContent = '没有更多内容了'; loadingDiv.style.background = 'rgba(255, 152, 0, 0.9)'; setTimeout(() => { loadingDiv.remove(); }, 2000); } isLoadingNextPage = false; }) .catch(error => { console.error('加载下一页失败:', error); loadingDiv.textContent = '✗ 加载失败'; loadingDiv.style.background = 'rgba(244, 67, 54, 0.9)'; setTimeout(() => { loadingDiv.remove(); }, 2000); isLoadingNextPage = false; }); } // 初始化 function init() { // 等待页面加载完成 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { createControlPanel(); autoHighlightFollowedPosts(); autoLoadNextPage(); }); } else { createControlPanel(); autoHighlightFollowedPosts(); autoLoadNextPage(); } } // 启动脚本 init(); })();