tampermonkey-script/51cg-helper.user.js
Ching L e04d4f447d fix: track middle-click and context menu opens as visited
- Added mousedown listener for middle-click (button 1) events
- Added contextmenu listener for right-click menu actions
- Ensures posts opened in new tabs are marked as visited
2025-09-23 10:49:45 +08:00

453 lines
14 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ==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');
}
}
});
// 处理鼠标中键点击(新标签页打开)
link.addEventListener('mousedown', function(e) {
// 中键点击 (button === 1)
if (e.button === 1) {
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');
}
}
}
});
// 处理右键菜单"在新标签页中打开"
link.addEventListener('contextmenu', function() {
// 延迟执行,让用户有机会选择菜单项
setTimeout(() => {
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');
}
}
}, 100);
});
});
}
// 处理热搜帖子的显示/隐藏
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 = `
<h4>51吃瓜助手</h4>
<div class="switch-container">
<label class="switch">
<input type="checkbox" id="hideHotSwitch">
<span class="slider"></span>
</label>
<span class="switch-label">隐藏热搜</span>
</div>
<div class="switch-container">
<label class="switch">
<input type="checkbox" id="markVisitedSwitch" checked>
<span class="slider"></span>
</label>
<span class="switch-label">标记已读</span>
</div>
`;
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();
}
})();