commit e0b022d791d9445b630d60a3fbc07eb81a86d755 Author: Ching Date: Sun Sep 21 20:31:00 2025 +0800 feat: Add 51cg website helper script with hot post filtering and read marking - Add toggle to hide hot trending posts and ads on list pages - Implement automatic read post tracking with visual indicators (strikethrough, grayscale) - Auto-manage read history (max 1000 records with FIFO) - Add floating control panel with persistent settings - Support dark mode and dynamic content loading - Fix issue where article content pages were incorrectly hidden - Update README with script documentation diff --git a/51cg-helper.user.js b/51cg-helper.user.js new file mode 100644 index 0000000..aa2774a --- /dev/null +++ b/51cg-helper.user.js @@ -0,0 +1,418 @@ +// ==UserScript== +// @name 51吃瓜网助手 +// @namespace http://tampermonkey.net/ +// @version 1.0.0 +// @description 隐藏热搜HOT帖子,标记已读帖子 +// @author Your Name +// @match https://51cg1.com/* +// @match https://www.51cg1.com/* +// @grant GM_setValue +// @grant GM_getValue +// @grant GM_addStyle +// @run-at document-end +// ==/UserScript== + +(function() { + 'use strict'; + + // 添加样式 + GM_addStyle(` + /* 控制按钮样式 */ + .cg-helper-controls { + position: fixed; + top: 100px; + right: 20px; + z-index: 9999; + background: white; + border-radius: 8px; + padding: 15px; + box-shadow: 0 2px 10px rgba(0,0,0,0.1); + min-width: 150px; + } + + .cg-helper-controls h4 { + margin: 0 0 10px 0; + font-size: 14px; + color: #333; + } + + /* 开关样式 */ + .switch-container { + display: flex; + align-items: center; + margin-bottom: 10px; + } + + .switch { + position: relative; + display: inline-block; + width: 50px; + height: 24px; + margin-right: 10px; + } + + .switch input { + opacity: 0; + width: 0; + height: 0; + } + + .slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + transition: .4s; + border-radius: 24px; + } + + .slider:before { + position: absolute; + content: ""; + height: 18px; + width: 18px; + left: 3px; + bottom: 3px; + background-color: white; + transition: .4s; + border-radius: 50%; + } + + input:checked + .slider { + background-color: #2196F3; + } + + input:checked + .slider:before { + transform: translateX(26px); + } + + .switch-label { + font-size: 13px; + color: #666; + } + + /* 隐藏热搜帖子和广告帖子的样式类 */ + .hot-post-hidden, + .ad-post-hidden { + display: none !important; + margin: 0 !important; + padding: 0 !important; + height: 0 !important; + overflow: hidden !important; + visibility: hidden !important; + position: absolute !important; + width: 0 !important; + } + + /* 确保隐藏article标签时不留空白,但排除内容页的article.post */ + article:not(.post).hot-post-hidden, + article:not(.post).ad-post-hidden { + display: none !important; + } + + /* 修复第一个可见帖子的上边距,但不影响内容页 */ + #index article:not(.hot-post-hidden):not(.ad-post-hidden):not(.post) { + margin-top: 0; + } + + /* 如果article被隐藏,确保下一个不会有额外间距 */ + #index article.hot-post-hidden + article, + #index article.ad-post-hidden + article { + margin-top: 0 !important; + } + + /* 已读帖子样式 - 标题删除线 + 图片黑白 */ + .post-card.visited .post-card-title { + text-decoration: line-through; + opacity: 0.6; + } + + /* 已读帖子的背景图片变黑白 */ + .post-card.visited .blog-background { + filter: grayscale(100%); + opacity: 0.7; + } + + /* 已读帖子整体透明度降低 */ + .post-card.visited { + opacity: 0.65; + } + + /* 深色模式适配 */ + body.theme-dark .cg-helper-controls { + background: #2b2b2b; + color: #e0e0e0; + } + + body.theme-dark .cg-helper-controls h4 { + color: #e0e0e0; + } + + body.theme-dark .switch-label { + color: #b0b0b0; + } + `); + + // 获取已访问的帖子列表 + function getVisitedPosts() { + const visited = GM_getValue('visitedPosts', '[]'); + try { + return JSON.parse(visited); + } catch (e) { + return []; + } + } + + // 保存已访问的帖子 + function saveVisitedPosts(posts) { + GM_setValue('visitedPosts', JSON.stringify(posts)); + } + + // 标记帖子为已访问 + function markPostAsVisited(postId) { + let visited = getVisitedPosts(); + + // 如果已存在,先移除旧的位置 + const existingIndex = visited.indexOf(postId); + if (existingIndex !== -1) { + visited.splice(existingIndex, 1); + } + + // 添加到末尾(最新的) + visited.push(postId); + + // 只保留最近的1000个记录,避免数据过大 + // 使用 while 循环确保数组长度不超过 1000 + while (visited.length > 1000) { + visited.shift(); // 移除最旧的记录 + } + + saveVisitedPosts(visited); + + // 输出调试信息 + console.log(`已标记帖子 ${postId} 为已读,当前已读记录数:${visited.length}`); + } + + // 应用已访问样式 + function applyVisitedStyles() { + const visited = getVisitedPosts(); + visited.forEach(postId => { + const postCard = document.getElementById(`post-card-${postId}`); + if (postCard) { + postCard.classList.add('visited'); + } + }); + } + + // 监听点击事件 + function setupClickListeners() { + // 监听所有帖子链接的点击 + document.querySelectorAll('a[href*="/archives/"]').forEach(link => { + link.addEventListener('click', function() { + const match = this.href.match(/\/archives\/(\d+)\//); + if (match) { + const postId = match[1]; + markPostAsVisited(postId); + // 立即应用样式 + const postCard = document.getElementById(`post-card-${postId}`); + if (postCard) { + postCard.classList.add('visited'); + } + } + }); + }); + } + + // 处理热搜帖子的显示/隐藏 + function toggleHotPosts(hide) { + // 判断是否在文章内容页面 + const isArticlePage = window.location.pathname.includes('/archives/') || + document.querySelector('article.post') !== null; + + // 如果是文章内容页面,不执行隐藏操作 + if (isArticlePage) { + console.log('文章内容页面,不执行隐藏操作'); + return; + } + + let hotCount = 0; + let adCount = 0; + + // 1. 查找所有包含"热搜 HOT"标签的帖子(仅在列表页) + document.querySelectorAll('.wrap .wraps').forEach(element => { + if (element.textContent.includes('热搜 HOT')) { + // 优先找到最近的 article 元素,但排除内容页的 article.post + let articleElement = element.closest('article'); + + // 确保不是文章内容页的主article + if (articleElement && !articleElement.classList.contains('post')) { + if (hide) { + // 使用 display: none 和 !important 确保完全隐藏 + articleElement.style.cssText = 'display: none !important;'; + articleElement.classList.add('hot-post-hidden'); + hotCount++; + } else { + // 恢复显示 + articleElement.style.cssText = ''; + articleElement.classList.remove('hot-post-hidden'); + } + } + } + }); + + // 2. 同时隐藏广告帖子(作者为AD1234或包含post-card-ads类) + // 只处理列表页的 article,不处理 article.post + document.querySelectorAll('article:not(.post)').forEach(article => { + // 检查是否是广告帖子 + const authorMeta = article.querySelector('meta[itemprop="name"]'); + const isAd = (authorMeta && authorMeta.content === 'AD1234') || + article.querySelector('.post-card-ads') || + article.querySelector('a[rel*="sponsored"]'); + + if (isAd) { + if (hide) { + article.style.cssText = 'display: none !important;'; + article.classList.add('ad-post-hidden'); + adCount++; + } else { + // 只恢复非广告的帖子 + if (article.classList.contains('ad-post-hidden')) { + article.style.cssText = ''; + article.classList.remove('ad-post-hidden'); + } + } + } + }); + + // 调试信息 + if (hide) { + console.log(`已隐藏 ${hotCount} 个热搜帖子,${adCount} 个广告帖子`); + } + } + + // 创建控制面板 + function createControlPanel() { + const panel = document.createElement('div'); + panel.className = 'cg-helper-controls'; + panel.innerHTML = ` +

51吃瓜助手

+
+ + 隐藏热搜 +
+
+ + 标记已读 +
+ `; + document.body.appendChild(panel); + + // 热搜开关 + const hideHotSwitch = document.getElementById('hideHotSwitch'); + hideHotSwitch.checked = GM_getValue('hideHot', false); + if (hideHotSwitch.checked) { + toggleHotPosts(true); + } + + hideHotSwitch.addEventListener('change', function() { + GM_setValue('hideHot', this.checked); + toggleHotPosts(this.checked); + }); + + // 标记已读开关 + const markVisitedSwitch = document.getElementById('markVisitedSwitch'); + markVisitedSwitch.checked = GM_getValue('markVisited', true); + + markVisitedSwitch.addEventListener('change', function() { + GM_setValue('markVisited', this.checked); + if (this.checked) { + applyVisitedStyles(); + } else { + // 移除所有已访问样式 + document.querySelectorAll('.post-card.visited').forEach(card => { + card.classList.remove('visited'); + }); + } + }); + } + + // 处理动态加载的内容 + function observeNewContent() { + const observer = new MutationObserver(function(mutations) { + mutations.forEach(function(mutation) { + if (mutation.addedNodes.length) { + // 重新应用已访问样式 + if (GM_getValue('markVisited', true)) { + applyVisitedStyles(); + } + // 重新应用热搜隐藏 + if (GM_getValue('hideHot', false)) { + toggleHotPosts(true); + } + // 重新设置点击监听 + setupClickListeners(); + } + }); + }); + + // 观察主内容区域的变化 + const contentArea = document.querySelector('.body-container-wrapper'); + if (contentArea) { + observer.observe(contentArea, { + childList: true, + subtree: true + }); + } + } + + // 初始化 + function init() { + // 清理超过限制的已读记录 + cleanupVisitedRecords(); + + // 创建控制面板 + createControlPanel(); + + // 应用保存的设置 + if (GM_getValue('hideHot', false)) { + toggleHotPosts(true); + } + + if (GM_getValue('markVisited', true)) { + applyVisitedStyles(); + } + + // 设置点击监听 + setupClickListeners(); + + // 监听动态内容 + observeNewContent(); + } + + // 清理超过限制的已读记录 + function cleanupVisitedRecords() { + let visited = getVisitedPosts(); + if (visited.length > 1000) { + // 只保留最新的 1000 条记录 + visited = visited.slice(-1000); + saveVisitedPosts(visited); + console.log(`已清理已读记录,保留最新的 1000 条`); + } + } + + // 等待DOM加载完成 + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); + } else { + init(); + } +})(); \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..95fe800 --- /dev/null +++ b/README.md @@ -0,0 +1,72 @@ +# Tampermonkey Scripts Collection + +一个 Tampermonkey 脚本集合,包含多个用于增强不同网站功能的脚本。 + +## 脚本列表 + +### 1. 51吃瓜网助手 (`51cg-helper.user.js`) + +用于增强 51吃瓜网 (51cg1.com) 浏览体验的脚本。 + +#### 功能特性 + +##### 隐藏热搜帖子 +- 一键隐藏所有带有"热搜 HOT"标签的帖子 +- 同时自动隐藏广告帖子 +- 开关状态会自动保存,刷新页面后保持设置 +- 仅在列表页生效,不影响文章内容页的正常显示 + +##### 标记已读帖子 +- 点击过的帖子会自动标记为已读状态 +- 已读帖子视觉效果: + - 标题添加删除线 + - 图片变为黑白色 + - 整体透明度降低 +- 自动管理已读记录: + - 最多保存 1000 条最近的已读记录 + - 超过限制自动删除最旧的记录(FIFO 策略) + - 无需手动清理 + +##### 控制面板 +- 固定在页面右侧的控制面板 +- 包含两个开关: + - 隐藏热搜:控制是否隐藏热搜和广告帖子 + - 标记已读:控制是否启用已读标记功能 +- 支持深色模式自动适配 + +#### 安装使用 +1. 安装 Tampermonkey 扩展 +2. 打开脚本文件 `51cg-helper.user.js` +3. 复制内容到 Tampermonkey 新建脚本 +4. 保存并启用脚本 +5. 访问 51cg1.com 即可看到右侧控制面板 + +--- + +### 2. Sort Jable Videos by Like Count (`sortbylikecount.js`) + +按点赞数排序 Jable 视频的脚本。 + +--- + +## 通用安装方法 + +1. **安装 Tampermonkey 扩展** + - [Chrome](https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo) + - [Firefox](https://addons.mozilla.org/firefox/addon/tampermonkey/) + - [Edge](https://microsoftedge.microsoft.com/addons/detail/tampermonkey/iikmkjmpaadaobahmlepeloendndfphd) + +2. **安装脚本** + - 打开 Tampermonkey 管理面板 + - 点击"添加新脚本" + - 复制对应脚本文件的内容 + - 保存脚本(Ctrl+S 或 Cmd+S) + +## 文件结构 + +``` +tampermonkey-script/ +├── README.md # 本文档 +├── 51cg-helper.user.js # 51吃瓜网助手脚本 +└── sortbylikecount.js # Jable 排序脚本 +``` diff --git a/sortbylikecount.js b/sortbylikecount.js new file mode 100644 index 0000000..e6a92c2 --- /dev/null +++ b/sortbylikecount.js @@ -0,0 +1,96 @@ +// ==UserScript== +// @name Sort Videos by Like Count +// @namespace http://tampermonkey.net/ +// @version 0.1 +// @description Sort videos by like count on a specified website +// @author NoName +// @match https://jable.tv/* +// @match https://jable.tv + +// @grant none +// ==/UserScript== +(function() { + 'use strict'; + + // Function to extract the like count from the sub-title element + function getLikeCount(subTitleElement) { + if (subTitleElement) { + const textContent = subTitleElement.textContent.trim(); + console.log('Full text content:', textContent); + + // Extract all numbers from the text content + const numbers = textContent.match(/\d+/g); + if (numbers && numbers.length > 0) { + const likeCount = parseInt(numbers[numbers.length - 1].replace(/\s/g, ''), 10); + console.log('Parsed like count:', likeCount); + return isNaN(likeCount) ? 0 : likeCount; + } + } + return 0; + } + + // Function to sort the videos within each parent div + function sortVideosInParent(parent) { + console.log('Sorting videos in parent:', parent); + const videoContainers = parent.querySelectorAll('.video-img-box'); + const videos = Array.from(videoContainers).map(v => { + const subTitle = v.querySelector('.sub-title'); + if (!subTitle) { + return null; + } + const vParent = v.parentElement; // 获取 v 的上一级 div + const likeCount = getLikeCount(subTitle); + console.log('Video element:', vParent, 'Like count:', likeCount); + return { element: vParent, likeCount: likeCount }; + }).filter(video => video !== null); + + videos.sort((a, b) => b.likeCount - a.likeCount); + + videos.forEach(video => { + parent.appendChild(video.element); + }); + } + + // Function to find all parent divs and sort their child videos + function sortAllVideos() { + const videoContainers = document.querySelectorAll('.video-img-box'); + const parents = new Set(); + + videoContainers.forEach(v => { + const parent = v.parentElement.parentElement; + console.log('Found parent:', parent); + parents.add(parent); + }); + + parents.forEach(parent => { + sortVideosInParent(parent); + }); + } + + // Add a sort button after the element with class "inactive-color fs-2 mb-0" + function addSortButton() { + const targetElement = document.querySelector('.inactive-color.fs-2.mb-0'); + if (targetElement) { + const sortItem = document.createElement('li'); + const sortLink = document.createElement('a'); + sortLink.href = '#'; + sortLink.textContent = '排序'; + sortLink.style.marginLeft = '10px'; + sortLink.addEventListener('click', function(event) { + event.preventDefault(); + sortAllVideos(); + }); + sortItem.appendChild(sortLink); + + targetElement.parentNode.insertBefore(sortItem, targetElement.nextSibling); + console.log('Sort button added after the target element'); + } + } + + + // Run the sort function after the DOM content has loaded + window.addEventListener('load', function() { + addSortButton(); + sortAllVideos(); + }) +})();