// ==UserScript==
// @name QQ空间精准广告拦截(保留好友视频)
// @namespace http://tampermonkey.net/
// @version 1.0
// @description 仅隐藏QQ空间中的广告动态,不误伤好友发布的视频内容
// @author You
// @match https://user.qzone.qq.com/*
// @match https://qzone.qq.com/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
// 广告特征选择器(组合多个特征,提高准确性)
const AD_SELECTORS = [
'i[name="feed_data"][data-uin="0"]', // 广告的data-uin固定为0
'[data-advfeed-click-url]', // 广告特有的点击追踪属性
'[data-advfeed-pv-url]', // 广告曝光追踪属性
'div.f-vqz-ad:has(span:contains("广告"))' // 含有“广告”标签的广告容器(:contains非标准,但可后续用js检查)
];
// 由于:contains不是标准选择器,这里用函数实现包含文本检查
function hasAdSpan(element) {
return element.querySelector('span')?.textContent.includes('广告');
}
// 判断一个元素是否为广告(或其子元素包含广告特征)
function isAdElement(element) {
// 直接匹配选择器
for (const selector of AD_SELECTORS) {
if (selector.includes(':contains')) {
// 特殊处理:contains,手动检查
if (element.matches && element.matches('div.f-vqz-ad') && hasAdSpan(element)) {
return true;
}
if (element.querySelector('div.f-vqz-ad') && hasAdSpan(element.querySelector('div.f-vqz-ad'))) {
return true;
}
} else {
if (element.matches && element.matches(selector)) return true;
if (element.querySelector(selector)) return true;
}
}
return false;
}
// 从广告特征元素向上查找整个feed项(通常是最接近的li元素)
function findFeedItem(element) {
// QQ空间每条动态通常是
,带有data-uin或feed-item类
return element.closest('li[data-uin], li.feed-item, li.f-single, li');
}
// 隐藏广告项(标记已处理,避免重复操作)
function hideAdIfNeeded(node) {
if (!node || node.nodeType !== Node.ELEMENT_NODE) return;
// 如果节点本身或子节点包含广告特征
if (isAdElement(node)) {
const feedItem = findFeedItem(node);
if (feedItem && feedItem.style.display !== 'none') {
feedItem.style.display = 'none';
// 可选:添加一个自定义属性标记已隐藏
feedItem.setAttribute('data-ad-hidden', 'true');
console.log('隐藏广告:', feedItem);
}
} else {
// 如果节点是容器,检查其内部是否有广告特征元素
const adElements = node.querySelectorAll(AD_SELECTORS.join(','));
adElements.forEach(el => {
// 排除已经隐藏的
const feedItem = findFeedItem(el);
if (feedItem && feedItem.style.display !== 'none') {
feedItem.style.display = 'none';
feedItem.setAttribute('data-ad-hidden', 'true');
console.log('隐藏广告:', feedItem);
}
});
}
}
// 初始隐藏已有的广告
function hideExistingAds() {
// 组合所有可能的选择器,一次性查找
const allAdCandidates = document.querySelectorAll(AD_SELECTORS.join(','));
allAdCandidates.forEach(el => {
const feedItem = findFeedItem(el);
if (feedItem && feedItem.style.display !== 'none') {
feedItem.style.display = 'none';
feedItem.setAttribute('data-ad-hidden', 'true');
}
});
// 额外处理包含“广告”文本的f-vqz-ad(因为:contains无法在querySelector中使用)
document.querySelectorAll('div.f-vqz-ad').forEach(div => {
if (hasAdSpan(div)) {
const feedItem = findFeedItem(div);
if (feedItem && feedItem.style.display !== 'none') {
feedItem.style.display = 'none';
feedItem.setAttribute('data-ad-hidden', 'true');
}
}
});
}
// 监听动态加载的新feed
function observeNewFeeds() {
// QQ空间的feed列表容器通常是 id="feed_friend" 的
const feedContainer = document.querySelector('#feed_friend');
if (!feedContainer) {
// 如果还没加载,等待一会儿再试
setTimeout(observeNewFeeds, 1000);
return;
}
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
hideAdIfNeeded(node);
});
});
});
observer.observe(feedContainer, { childList: true, subtree: true });
}
// 启动
hideExistingAds();
observeNewFeeds();
// 由于页面可能多次刷新/切换,使用MutationObserver监听整个文档以应对动态改变
const bodyObserver = new MutationObserver(mutations => {
// 当整个文档结构发生较大变化时(如QQ空间切换了不同模块),重新执行初始隐藏
// 简单起见,每次检测到子树的较大变化,重新检查隐藏(但注意不要重复隐藏已经隐藏的)
// 为了避免性能问题,可以限制频率或只在特定容器变化时触发
hideExistingAds();
});
bodyObserver.observe(document.body, { childList: true, subtree: true });
})();