// ==UserScript== // @name 贴吧广告过滤登录去除 // @namespace noting // @version 0.8.2 // @description 贴吧广告过滤,自动关闭登录弹窗,控制面板可拖动(优化版+分页快捷) // @author Time (优化: 面板增强+分页功能) // @match https://tieba.baidu.com/* // @grant none // @license MIT // @downloadURL none // ==/UserScript== (function() { 'use strict'; // 配置参数 const options = { expensionName: "贴吧广告过滤登录去除", interval: 500, development: false, isDomRemove: false, showAds: false, matchingHost: "tieba.baidu.com", loginDomId: "tiebaCustomPassLogin", adTextClass: "label_text", adText: "广告", adIds: [ "pagelet_frs-aside/pagelet/fengchao_ad", "banner_pb_customize", "plat_recom_carousel" ], adClasses: [ "fengchao-wrap-feed", "head_banner", "head_ad_pop", "l_banner", "j_couplet", "card_banner", "bus-top-activity-wrap" ], page:{ container:"pb_list_pager", // 分页容器class down:"下一页", // 下一页文本 up:"上一页" // 上一页文本 }, panelStyle: { width: "240px", minHeight: "240px", // 增加高度容纳分页功能 top: "120px", left: "15px", zIndex: 9999, border: "1px solid #e5e7eb", borderColor: "#e5e7eb", bgColor: "#ffffff", shadow: "0 4px 12px rgba(0,0,0,0.08)", headerBg: "#2563eb", headerColor: "#ffffff", textColor: "#374151", subTextColor: "#6b7280", checkboxSize: "16px", btnBg: "#f3f4f6", btnHoverBg: "#e5e7eb", btnRadius: "4px", marginBottom: "12px", padding: "12px" } }; // 工具函数 - 日志输出 const logger = { log: (...args) => { if (options.development) { console.log(`[${options.expensionName}]`, ...args); } }, error: (...args) => { console.error(`[${options.expensionName}]`, ...args); } }; // 广告检测与处理模块 const AdManager = { detectedAds: new Set(), isAdDetected: (element) => { return AdManager.detectedAds.has(element); }, addAdElement: (element) => { if (!element || AdManager.isAdDetected(element)) return; AdManager.detectedAds.add(element); AdManager.handleAdElement(element); const observer = new MutationObserver((mutations) => { if (!document.body.contains(element)) { AdManager.detectedAds.delete(element); observer.disconnect(); } }); observer.observe(document.body, { childList: true, subtree: true }); }, handleAdElement: (element) => { if (!element || !element.style) return; try { if (options.showAds) { element.style.display = ""; } else { if (options.isDomRemove) { element.remove(); AdManager.detectedAds.delete(element); } else if (element.style.display !== "none") { element.style.display = "none"; } } } catch (ex) { logger.error("处理广告元素出错:", ex); } }, handleAllAds: () => { AdManager.detectedAds.forEach(element => { AdManager.handleAdElement(element); }); } }; // 检测器模块 const Detector = { detectLogin: () => { try { const loginElement = document.getElementById(options.loginDomId); if (loginElement) { AdManager.addAdElement(loginElement); } } catch (ex) { logger.error("检测登录弹窗出错:", ex); } }, detectBySelector: (type, selectors) => { if (!selectors || !Array.isArray(selectors) || selectors.length === 0) return; selectors.forEach(selector => { try { let elements; if (type === 'id') { const el = document.getElementById(selector); if (el) elements = [el]; } else if (type === 'class') { elements = document.getElementsByClassName(selector); } if (elements && elements.length > 0) { Array.from(elements).forEach(el => AdManager.addAdElement(el)); } } catch (ex) { logger.error(`检测广告元素 ${selector} 出错:`, ex); } }); }, detectByAdText: () => { try { const adTextElements = document.getElementsByClassName(options.adTextClass); Array.from(adTextElements).forEach(element => { if (element.innerText.trim() === options.adText) { let parent = element.closest('div, section, article'); if (parent) { AdManager.addAdElement(parent); } } }); } catch (ex) { logger.error("检测文字标记广告出错:", ex); } }, detectAll: () => { Detector.detectLogin(); Detector.detectBySelector('class', options.adClasses); Detector.detectBySelector('id', options.adIds); Detector.detectByAdText(); } }; // 分页工具模块 - 处理上/下一页链接的查找与点击 const PaginationTool = { /** * 查找分页容器内的目标链接(上一页/下一页) * @param {string} targetText - 目标文本(上一页/下一页) * @returns {HTMLElement|null} - 找到的a标签,未找到则返回null */ findPageLink: (targetText) => { try { // 获取分页容器(class为pb_list_pager) const pagerContainer = document.getElementsByClassName(options.page.container)[0]; if (!pagerContainer) { logger.log("未找到分页容器(class: pb_list_pager)"); return null; } // 查找容器内所有a标签,筛选文本匹配的链接 const linkElements = pagerContainer.getElementsByTagName('a'); for (let link of linkElements) { // 去除文本空格后匹配(避免换行/空格影响) if (link.textContent.trim() === targetText) { return link; } } logger.log(`未找到文本为"${targetText}"的链接`); return null; } catch (ex) { logger.error("查找分页链接出错:", ex); return null; } }, /** * 触发目标分页链接的点击(上一页/下一页) * @param {string} targetText - 目标文本(上一页/下一页) * @returns {boolean} - 点击成功返回true,失败返回false */ triggerPageClick: (targetText) => { const targetLink = PaginationTool.findPageLink(targetText); if (targetLink) { // 模拟真实点击(兼容部分动态绑定事件的链接) targetLink.click(); logger.log(`已触发"${targetText}"点击`); return true; } return false; }, /** * 更新分页按钮状态(是否禁用) * @param {HTMLButtonElement} prevBtn - 上一页按钮 * @param {HTMLButtonElement} nextBtn - 下一页按钮 */ updateButtonStates: (prevBtn, nextBtn) => { // 检查上一页链接是否存在 const hasPrev = PaginationTool.findPageLink(options.page.up) !== null; prevBtn.disabled = !hasPrev; prevBtn.style.opacity = hasPrev ? "1" : "0.6"; prevBtn.style.cursor = hasPrev ? "pointer" : "not-allowed"; // 检查下一页链接是否存在 const hasNext = PaginationTool.findPageLink(options.page.down) !== null; nextBtn.disabled = !hasNext; nextBtn.style.opacity = hasNext ? "1" : "0.6"; nextBtn.style.cursor = hasNext ? "pointer" : "not-allowed"; } }; // 控制面板模块 - 包含分页功能 const ControlPanel = { panelElement: null, /** * 创建控制面板,包含分页快捷操作 */ createPanel: () => { // 1. 创建面板容器 const panel = document.createElement("div"); panel.id = "ad-filter-panel"; panel.style.cssText = ` position: fixed; width: ${options.panelStyle.width}; min-height: ${options.panelStyle.minHeight}; top: ${options.panelStyle.top}; left: ${options.panelStyle.left}; z-index: ${options.panelStyle.zIndex}; border: ${options.panelStyle.border}; border-radius: 6px; background-color: ${options.panelStyle.bgColor}; box-shadow: ${options.panelStyle.shadow}; overflow: hidden; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; transition: box-shadow 0.2s ease; `; // 2. 面板头部(拖拽区) const header = document.createElement("div"); header.className = "panel-header"; header.style.cssText = ` padding: ${options.panelStyle.padding}; background-color: ${options.panelStyle.headerBg}; color: ${options.panelStyle.headerColor}; font-size: 15px; font-weight: 500; cursor: move; display: flex; justify-content: space-between; align-items: center; `; header.innerHTML = ` ${options.expensionName} v${GM_info?.script?.version || '0.8.2'} `; // 3. 面板内容区 const content = document.createElement("div"); content.className = "panel-content"; content.style.cssText = ` padding: ${options.panelStyle.padding}; color: ${options.panelStyle.textColor}; font-size: 14px; `; // 3.1 广告显示/隐藏开关 const adToggleGroup = document.createElement("div"); adToggleGroup.style.cssText = ` display: flex; align-items: center; margin-bottom: ${options.panelStyle.marginBottom}; padding-bottom: ${options.panelStyle.marginBottom}; border-bottom: 1px solid #f3f4f6; `; const adToggle = document.createElement("input"); adToggle.type = "checkbox"; adToggle.id = "ad-toggle"; adToggle.checked = options.showAds; adToggle.style.cssText = ` width: ${options.panelStyle.checkboxSize}; height: ${options.panelStyle.checkboxSize}; margin-right: 8px; cursor: pointer; `; const adToggleLabel = document.createElement("label"); adToggleLabel.htmlFor = "ad-toggle"; adToggleLabel.textContent = "显示广告(默认隐藏)"; adToggleLabel.style.cursor = "pointer"; adToggleGroup.append(adToggle, adToggleLabel); // 3.2 广告删除方式 const deleteModeGroup = document.createElement("div"); deleteModeGroup.style.cssText = ` display: flex; align-items: center; margin-bottom: ${options.panelStyle.marginBottom}; padding-bottom: ${options.panelStyle.marginBottom}; border-bottom: 1px solid #f3f4f6; `; const deleteModeToggle = document.createElement("input"); deleteModeToggle.type = "checkbox"; deleteModeToggle.id = "delete-mode-toggle"; deleteModeToggle.checked = options.isDomRemove; deleteModeToggle.style.cssText = ` width: ${options.panelStyle.checkboxSize}; height: ${options.panelStyle.checkboxSize}; margin-right: 8px; cursor: pointer; `; const deleteModeLabel = document.createElement("label"); deleteModeLabel.htmlFor = "delete-mode-toggle"; deleteModeLabel.innerHTML = ` 彻底删除广告(默认隐藏)
注:彻底删除可减少页面占用,但可能影响部分页面布局 `; deleteModeLabel.style.cursor = "pointer"; deleteModeGroup.append(deleteModeToggle, deleteModeLabel); // 3.3 分页快捷操作区 const paginationGroup = document.createElement("div"); paginationGroup.style.cssText = ` margin-bottom: ${options.panelStyle.marginBottom}; padding-bottom: ${options.panelStyle.marginBottom}; border-bottom: 1px solid #f3f4f6; `; // 分页标题 const paginationTitle = document.createElement("div"); paginationTitle.style.cssText = ` font-size: 13px; margin-bottom: 8px; color: ${options.panelStyle.subTextColor}; `; paginationTitle.textContent = "分页快捷操作"; // 上/下一页按钮容器 const pageBtnContainer = document.createElement("div"); pageBtnContainer.style.cssText = "display: flex; gap: 8px;"; // 上一页按钮 const prevPageBtn = document.createElement("button"); prevPageBtn.id = "prev-page-btn"; prevPageBtn.textContent = options.page.up; prevPageBtn.style.cssText = ` flex: 1; padding: 8px 0; background-color: ${options.panelStyle.btnBg}; border: none; border-radius: ${options.panelStyle.btnRadius}; color: ${options.panelStyle.textColor}; font-size: 13px; cursor: pointer; transition: background-color 0.2s ease; `; // 下一页按钮 const nextPageBtn = document.createElement("button"); nextPageBtn.id = "next-page-btn"; nextPageBtn.textContent = options.page.down; nextPageBtn.style.cssText = ` flex: 1; padding: 8px 0; background-color: ${options.panelStyle.btnBg}; border: none; border-radius: ${options.panelStyle.btnRadius}; color: ${options.panelStyle.textColor}; font-size: 13px; cursor: pointer; transition: background-color 0.2s ease; `; // 按钮hover效果 prevPageBtn.onmouseover = () => { if (!prevPageBtn.disabled) { prevPageBtn.style.backgroundColor = options.panelStyle.btnHoverBg; } }; prevPageBtn.onmouseout = () => { if (!prevPageBtn.disabled) { prevPageBtn.style.backgroundColor = options.panelStyle.btnBg; } }; nextPageBtn.onmouseover = () => { if (!nextPageBtn.disabled) { nextPageBtn.style.backgroundColor = options.panelStyle.btnHoverBg; } }; nextPageBtn.onmouseout = () => { if (!nextPageBtn.disabled) { nextPageBtn.style.backgroundColor = options.panelStyle.btnBg; } }; // 组装分页操作区 pageBtnContainer.append(prevPageBtn, nextPageBtn); paginationGroup.append(paginationTitle, pageBtnContainer); // 3.4 手动刷新广告检测 const refreshBtn = document.createElement("button"); refreshBtn.id = "refresh-ad-detect"; refreshBtn.textContent = "手动刷新广告检测"; refreshBtn.style.cssText = ` width: 100%; padding: 8px 0; background-color: ${options.panelStyle.btnBg}; border: none; border-radius: ${options.panelStyle.btnRadius}; color: ${options.panelStyle.textColor}; font-size: 13px; cursor: pointer; transition: background-color 0.2s ease; margin-bottom: ${options.panelStyle.marginBottom}; `; refreshBtn.onmouseover = () => { refreshBtn.style.backgroundColor = options.panelStyle.btnHoverBg; }; refreshBtn.onmouseout = () => { refreshBtn.style.backgroundColor = options.panelStyle.btnBg; }; // 3.5 广告统计显示 const adCount = document.createElement("div"); adCount.id = "ad-count"; adCount.style.cssText = ` font-size: 12px; color: ${options.panelStyle.subTextColor}; text-align: right; padding-top: 8px; border-top: 1px dashed #f3f4f6; `; adCount.textContent = `已过滤广告:${AdManager.detectedAds.size} 个`; // 4. 组装内容区 content.append(adToggleGroup, deleteModeGroup, paginationGroup, refreshBtn, adCount); panel.append(header, content); document.body.appendChild(panel); ControlPanel.panelElement = panel; // 5. 绑定事件 ControlPanel.bindEvents(adToggle, deleteModeToggle, refreshBtn, adCount, prevPageBtn, nextPageBtn); // 6. 初始化拖拽 ControlPanel.initDrag(); // 7. 面板hover效果 panel.onmouseover = () => { panel.style.boxShadow = "0 6px 16px rgba(0,0,0,0.12)"; }; panel.onmouseout = () => { panel.style.boxShadow = options.panelStyle.shadow; }; return panel; }, /** * 绑定面板事件 */ bindEvents: (adToggle, deleteModeToggle, refreshBtn, adCount, prevPageBtn, nextPageBtn) => { // 广告显示/隐藏切换 adToggle.addEventListener('change', (e) => { options.showAds = e.target.checked; AdManager.handleAllAds(); }); // 广告删除模式切换 deleteModeToggle.addEventListener('change', (e) => { options.isDomRemove = e.target.checked; AdManager.handleAllAds(); if (options.isDomRemove) { AdManager.detectedAds.forEach(el => { if (document.body.contains(el)) el.remove(); }); AdManager.detectedAds.clear(); } }); // 手动刷新广告检测 refreshBtn.addEventListener('click', () => { refreshBtn.textContent = "检测中..."; refreshBtn.disabled = true; Detector.detectAll(); AdManager.handleAllAds(); setTimeout(() => { refreshBtn.textContent = "手动刷新广告检测"; refreshBtn.disabled = false; adCount.textContent = `已过滤广告:${AdManager.detectedAds.size} 个`; // 刷新分页按钮状态 PaginationTool.updateButtonStates(prevPageBtn, nextPageBtn); }, 1000); }); // 定时更新广告统计和分页按钮状态 setInterval(() => { adCount.textContent = `已过滤广告:${AdManager.detectedAds.size} 个`; PaginationTool.updateButtonStates(prevPageBtn, nextPageBtn); }, 3000); // 上一页按钮点击事件 prevPageBtn.addEventListener('click', () => { if (!prevPageBtn.disabled) { PaginationTool.triggerPageClick(options.page.up); // 点击后短暂禁用按钮,防止重复点击 prevPageBtn.disabled = true; setTimeout(() => { PaginationTool.updateButtonStates(prevPageBtn, nextPageBtn); }, 1000); } }); // 下一页按钮点击事件 nextPageBtn.addEventListener('click', () => { if (!nextPageBtn.disabled) { PaginationTool.triggerPageClick(options.page.down); // 点击后短暂禁用按钮,防止重复点击 nextPageBtn.disabled = true; setTimeout(() => { PaginationTool.updateButtonStates(prevPageBtn, nextPageBtn); }, 1000); } }); // 初始更新分页按钮状态 PaginationTool.updateButtonStates(prevPageBtn, nextPageBtn); }, /** * 初始化拖拽功能 */ initDrag: () => { const panel = ControlPanel.panelElement; const dragHandle = panel.querySelector(".panel-header"); let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; let isDragging = false; dragHandle.addEventListener('mousedown', (e) => { e.preventDefault(); isDragging = true; panel.style.boxShadow = "0 8px 24px rgba(0,0,0,0.15)"; panel.style.transition = "box-shadow 0.1s ease"; pos3 = e.clientX; pos4 = e.clientY; document.addEventListener('mousemove', dragMove); document.addEventListener('mouseup', dragEnd); }); const dragMove = (e) => { if (!isDragging) return; e.preventDefault(); pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY; pos3 = e.clientX; pos4 = e.clientY; const newTop = panel.offsetTop - pos2; const newLeft = panel.offsetLeft - pos1; const viewportWidth = window.innerWidth; const viewportHeight = window.innerHeight; panel.style.top = `${Math.max(0, Math.min(newTop, viewportHeight - panel.offsetHeight))}px`; panel.style.left = `${Math.max(0, Math.min(newLeft, viewportWidth - panel.offsetWidth))}px`; }; const dragEnd = () => { isDragging = false; panel.style.boxShadow = options.panelStyle.shadow; panel.style.transition = "box-shadow 0.2s ease"; document.removeEventListener('mousemove', dragMove); document.removeEventListener('mouseup', dragEnd); }; } }; // 初始化函数 const init = () => { if (window.location.host.indexOf(options.matchingHost) === -1) { logger.log("不匹配目标网站,脚本不执行"); return; } ControlPanel.createPanel(); const checkInterval = setInterval(() => { if (document.readyState === 'unloaded') { clearInterval(checkInterval); return; } Detector.detectAll(); AdManager.handleAllAds(); }, options.interval); window.addEventListener('beforeunload', () => { clearInterval(checkInterval); }); logger.log("脚本初始化完成"); }; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();