[JavaScript] 纯文本查看 复制代码
// ==UserScript==
// [url=home.php?mod=space&uid=170990]@name[/url] 虎嗅评论过滤 - 屏蔽评论数超过100页的用户
// [url=home.php?mod=space&uid=467642]@namespace[/url] http://tampermonkey.net/
// [url=home.php?mod=space&uid=1248337]@version[/url] 1.0.1
// @description 自动检测并屏蔽评论数超过100页的用户评论
// [url=home.php?mod=space&uid=686208]@AuThor[/url] You
// [url=home.php?mod=space&uid=195849]@match[/url] https://www.huxiu.com/moment/*
// @exclude https://www.huxiu.com/member/*
// [url=home.php?mod=space&uid=609072]@grant[/url] GM_xmlhttpRequest
// [url=home.php?mod=space&uid=67665]@connect[/url] api-web-account.huxiu.com
// ==/UserScript==
(function() {
'use strict';
// 配置
const MAX_PAGES = 100; // 最大允许的评论页数
const API_URL = 'https://api-web-account.huxiu.com/web/comment/commentList';
const CHECK_INTERVAL = 2000; // 检查新评论的间隔(毫秒)
// 存储正在检查中的用户ID,避免重复请求
const pendingChecks = new Set();
/**
* 从评论元素中提取用户ID
*/
function extractUserId(commentElement) {
// 尝试多种方式提取用户ID
// 方式1: 从虎嗅会员链接中提取(最常见,格式:/member/2374684.html)
const userLinks = commentElement.querySelectorAll('a[href*="/member/"]');
for (const link of userLinks) {
const href = link.getAttribute('href');
// 匹配 /member/123456.html 或 /member/123456 格式
let match = href.match(/\/member\/(\d+)(?:\.html)?/);
if (match) return match[1];
}
// 方式2: 从其他用户链接格式中提取
const otherLinks = commentElement.querySelectorAll('a[href*="/user/"], a[href*="uid="]');
for (const link of otherLinks) {
const href = link.getAttribute('href');
// 匹配 /user/123456 格式
let match = href.match(/\/user\/(\d+)/);
if (match) return match[1];
// 匹配 ?uid=123456 格式
match = href.match(/[?&]uid=(\d+)/);
if (match) return match[1];
}
// 方式2: 从data属性中提取
let element = commentElement;
for (let i = 0; i < 10 && element; i++) {
const dataUid = element.getAttribute('data-uid') ||
element.getAttribute('data-user-id') ||
element.getAttribute('uid');
if (dataUid && /^\d+$/.test(dataUid)) {
return dataUid;
}
element = element.parentElement;
}
// 方式3: 从class或id中提取
element = commentElement;
for (let i = 0; i < 5 && element; i++) {
const uidMatch = element.className?.match(/uid[_-]?(\d+)|user[_-]?(\d+)/i) ||
element.id?.match(/uid[_-]?(\d+)|user[_-]?(\d+)/i);
if (uidMatch) {
return uidMatch[1] || uidMatch[2];
}
element = element.parentElement;
}
// 方式4: 从图片src或其他属性中提取
const img = commentElement.querySelector('img[src*="user"], img[src*="avatar"]');
if (img) {
const src = img.getAttribute('src');
const match = src?.match(/user[\/_-]?(\d+)/i);
if (match) return match[1];
}
// 调试:输出元素信息
console.warn('无法提取用户ID,元素信息:', {
className: commentElement.className,
id: commentElement.id,
innerHTML: commentElement.innerHTML.substring(0, 200)
});
return null;
}
/**
* 获取用户评论总数
*/
function getUserCommentPages(uid) {
return new Promise((resolve, reject) => {
// 如果正在检查中,等待
if (pendingChecks.has(uid)) {
setTimeout(() => {
getUserCommentPages(uid).then(resolve).catch(reject);
}, 500);
return;
}
pendingChecks.add(uid);
GM_xmlhttpRequest({
method: 'POST',
url: API_URL,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json',
'Referer': 'https://www.huxiu.com/',
'Origin': 'https://www.huxiu.com'
},
data: `platform=www&page=1&uid=${uid}`,
onload: function(response) {
pendingChecks.delete(uid);
try {
const data = JSON.parse(response.responseText);
// 调试:输出API响应结构
console.log(`[API] 用户 ${uid} 的API响应:`, JSON.stringify(data, null, 2));
// 尝试多种可能的响应格式
let totalPages = 0;
if (data && data.data) {
// 格式1: 虎嗅API标准格式 { data: { total_page: xxx } }
if (data.data.total_page !== undefined && data.data.total_page !== null) {
totalPages = parseInt(data.data.total_page);
console.log(`[API] 从 data.data.total_page 获取页数: ${totalPages}`);
} else {
console.warn(`[API] 用户 ${uid} 的响应中未找到 total_page 字段,data.data 内容:`, data.data);
}
} else {
console.warn(`[API] 用户 ${uid} 的响应格式异常,data 或 data.data 不存在:`, data);
}
if (totalPages === 0) {
console.warn(`[API] 用户 ${uid} 的页数解析为0,可能解析失败`);
}
resolve(totalPages);
} catch (e) {
console.error('解析API响应失败:', e, response.responseText);
reject(e);
}
},
onerror: function(error) {
pendingChecks.delete(uid);
console.error('API请求失败:', error);
reject(error);
}
});
});
}
/**
* 隐藏评论元素
* [url=home.php?mod=space&uid=952169]@Param[/url] {HTMLElement} commentElement - 评论元素
* @param {string} uid - 用户ID
* @param {number} totalPages - 总评论页数
*/
function hideComment(commentElement, uid, totalPages) {
commentElement.style.display = 'none';
commentElement.setAttribute('data-filtered', 'true');
// 在控制台输出屏蔽信息
console.log(`🚫 已屏蔽用户评论 | 用户ID: ${uid} | 总评论页数: ${totalPages}页`);
// 添加一个标记,方便调试
const marker = document.createElement('div');
marker.style.cssText = 'padding: 5px; background: #ffebee; color: #c62828; font-size: 12px;';
marker.textContent = `已屏蔽:该用户评论数超过100页(用户ID: ${uid}, 共${totalPages}页)`;
commentElement.parentNode.insertBefore(marker, commentElement);
}
/**
* 检查并过滤单个评论
*/
async function checkAndFilterComment(commentElement) {
// 如果已经处理过,跳过
if (commentElement.getAttribute('data-checked') === 'true' ||
commentElement.getAttribute('data-filtered') === 'true') {
return;
}
const uid = extractUserId(commentElement);
if (!uid) {
console.warn('无法提取用户ID:', commentElement);
return;
}
// 标记为已检查
commentElement.setAttribute('data-checked', 'true');
try {
const totalPages = await getUserCommentPages(uid);
console.log(`用户 ${uid} 的评论页数: ${totalPages}`);
if (totalPages > MAX_PAGES) {
hideComment(commentElement, uid, totalPages);
}
} catch (error) {
console.error(`检查用户 ${uid} 失败:`, error);
// 出错时不隐藏,避免误杀
}
}
/**
* 查找页面上的所有评论元素
*/
function findAllComments() {
// 根据虎嗅网站的实际结构,优先使用 .comment-item
const selectors = [
'.comment-item', // 虎嗅评论的标准选择器
'.moment-comment__list .comment-item', // 更精确的选择器
'[data-comment-id]', // 通过data-comment-id属性
'.comment',
'.moment-comment',
'[class*="comment"]',
'[class*="Comment"]', // 驼峰命名
'li[class*="comment"]',
'div[class*="comment"]',
'.comment-list li',
'.comment-list > div',
];
const comments = new Set();
for (const selector of selectors) {
try {
const elements = document.querySelectorAll(selector);
elements.forEach(el => {
// 确保不是已经过滤的元素,且有实际内容
if (el.getAttribute('data-filtered') !== 'true' &&
el.offsetHeight > 0 && // 确保元素可见
el.textContent.trim().length > 0) { // 确保有内容
comments.add(el);
}
});
} catch (e) {
// 忽略无效选择器
}
}
// 去重:如果元素A包含元素B,只保留A
const filtered = Array.from(comments).filter(comment => {
return !Array.from(comments).some(other =>
other !== comment && other.contains(comment)
);
});
return filtered;
}
/**
* 批量检查评论
*/
async function checkAllComments() {
const comments = findAllComments();
console.log(`找到 ${comments.length} 条评论,开始检查...`);
// 批量处理,避免同时发起太多请求
const batchSize = 5;
for (let i = 0; i < comments.length; i += batchSize) {
const batch = comments.slice(i, i + batchSize);
await Promise.all(batch.map(comment => checkAndFilterComment(comment)));
// 批次之间稍作延迟
if (i + batchSize < comments.length) {
await new Promise(resolve => setTimeout(resolve, 500));
}
}
}
/**
* 监听DOM变化,处理动态加载的评论
*/
function setupMutationObserver() {
const observer = new MutationObserver((mutations) => {
let shouldCheck = false;
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === 1) { // Element node
// 检查是否是评论相关的元素
if (node.classList && (
node.classList.toString().includes('comment') ||
node.querySelector && node.querySelector('[class*="comment"]')
)) {
shouldCheck = true;
}
}
});
});
if (shouldCheck) {
// 延迟检查,等待DOM完全渲染
setTimeout(() => {
checkAllComments();
}, 1000);
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
/**
* 初始化
*/
function init() {
// 排除个人中心页面
if (window.location.pathname.match(/^\/member\//)) {
console.log('虎嗅评论过滤插件:跳过个人中心页面');
return;
}
console.log('虎嗅评论过滤插件已启动(无缓存模式)');
// 等待页面加载完成
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
setTimeout(checkAllComments, 2000);
setupMutationObserver();
});
} else {
setTimeout(checkAllComments, 2000);
setupMutationObserver();
}
// 定期检查新评论
setInterval(checkAllComments, CHECK_INTERVAL);
}
// 启动
init();
})();