您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
移除页面aff参数,并为代码块增加一键复制按钮。
当前为
// ==UserScript== // @name 没有朋友 // @namespace https://linux.do/u/amna/ // @version 2.0 // @description 移除页面aff参数,并为代码块增加一键复制按钮。 // @license MIT // @author https://linux.do/u/amna/ // @match *://ygpy.net/* // @run-at document-end // @grant GM_setClipboard // @grant GM_addStyle // ==/UserScript== (function() { 'use strict'; // --- 全局常量和配置 --- // 【重构】将常量集中管理,便于维护。 const SCRIPT_BUTTON_CLASS = 'amna-copy-button'; // 使用更具辨识度的类名 const DEBOUNCE_DELAY = 250; //ms /** * @description 需要忽略的文本内容,不为其添加复制按钮。 * @type {Set<string>} */ const IGNORED_CODE_TEXT = new Set([ "流媒体解锁", "多设备在线", "支持 BT 下载", "国内中转线路", "IEPL 专线路", "设备不限制", "晚高峰稳定", "国际专线", "IPLC 线路", "50+ 节点", "不限设备", "超大带宽", "赠送 Emby", "IEPL 专线", "家宽 IP", "厦门专线", "开始阅读", "仪表盘", "主页", "兑换礼品卡" ]); // --- 工具函数 --- /** * @description 防抖函数,用于延迟执行,合并高频触发的事件。 * @param {Function} func - 需要防抖的函数。 * @param {number} wait - 延迟的毫秒数。 * @returns {Function} - 包装后的防抖函数。 */ function debounce(func, wait) { let timeout; return function(...args) { clearTimeout(timeout); timeout = setTimeout(() => func.apply(this, args), wait); }; } /** * @description 惰性注入CSS样式,确保只执行一次。 * @returns {void} */ const injectStyles = (() => { let injected = false; return () => { if (injected) return; GM_addStyle(` .${SCRIPT_BUTTON_CLASS} { margin-left: 8px; padding: 3px 6px; border: 1px solid #ccc; border-radius: 4px; background-color: #f0f0f0; color: #333; cursor: pointer; font-size: 12px; font-family: sans-serif; user-select: none; display: inline-block; vertical-align: middle; opacity: 0.7; transition: opacity 0.2s, background-color 0.2s; } .${SCRIPT_BUTTON_CLASS}:hover { background-color: #e0e0e0; border-color: #bbb; opacity: 1; } .${SCRIPT_BUTTON_CLASS}:active { background-color: #d0d0d0; } `); injected = true; }; })(); // --- 核心功能模块 --- /** * @description 净化指定容器内的链接,移除查询参数(包括在hash中的参数)。 * @param {HTMLElement} container - 要扫描的DOM元素。 */ function cleanLinks(container) { const links = container.querySelectorAll('a[href*="?"]'); links.forEach(link => { // 只处理有 href 属性的有效链接 if (!link.href) return; try { // 使用 link.href 可以获取到完整的绝对路径 URL const url = new URL(link.href); const hasSearchParams = url.search; const hasHashParams = url.hash && url.hash.includes('?'); // 【关键修复】只有在 search 或 hash 中存在查询参数时才进行处理 if (hasSearchParams || hasHashParams) { let newHref = url.origin + url.pathname; if (hasHashParams) { newHref += url.hash.split('?')[0]; } link.href = newHref; } } catch (error) { // 对于无效URL(如 "javascript:;"),URL构造函数会抛出异常,这里进行降级处理。 // 这个简单的字符串分割可以同时处理标准查询和哈希内查询。 if (link.href.includes('?')) { link.href = link.href.split('?')[0]; } } }); } /** * @description 为指定容器内的 <code> 标签添加复制按钮。 * @param {HTMLElement} container - 要扫描的DOM元素。 */ function addCopyButtons(container) { injectStyles(); // 确保样式已注入 // 【优化】查询时直接排除已经处理过的(即内部有我们按钮兄弟的)code的父元素 const codeBlocks = container.querySelectorAll('code:not([data-amna-processed])'); codeBlocks.forEach(code => { code.setAttribute('data-amna-processed', 'true'); // 【优化】使用 data 属性标记,比检查兄弟节点更可靠 const textContent = code.textContent?.trim(); if (!textContent || IGNORED_CODE_TEXT.has(textContent)) { return; } // 【重构】使用更可靠的标记来避免重复添加 if (code.nextElementSibling?.classList.contains(SCRIPT_BUTTON_CLASS)) { return; } const copyButton = document.createElement('button'); copyButton.textContent = '复制'; copyButton.className = SCRIPT_BUTTON_CLASS; copyButton.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); GM_setClipboard(textContent, 'text'); copyButton.textContent = '已复制!'; copyButton.disabled = true; setTimeout(() => { copyButton.textContent = '复制'; copyButton.disabled = false; }, 2000); }); // 【优化】使用 .after() 方法,代码更简洁 code.after(copyButton); }); } // --- 主执行逻辑与DOM监控 --- /** * @description 统一处理节点,应用所有功能。 * @param {Node} node - 要处理的DOM节点, 通常是 document.body 或动态添加的元素。 */ function processNode(node) { // 确保是元素节点 if (!(node instanceof Element)) return; cleanLinks(node); addCopyButtons(node); } // 防抖包装的主处理函数 const debouncedProcessAll = debounce(() => { console.log('[没有朋友] DOM发生变化,重新扫描页面...'); processNode(document.body); }, DEBOUNCE_DELAY); // 启动脚本 function main() { console.log('[没有朋友] 脚本已启动。'); // 1. 立即处理页面现有内容 processNode(document.body); // 2. 监听未来的DOM变化 const observer = new MutationObserver((mutations) => { // 【优化】对 mutations 进行更精细的检查,只关心添加的元素节点 for (const mutation of mutations) { if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { // 只要有节点添加,就触发防抖检查,无需检查是否是自己的按钮, // 因为我们的 addCopyButtons 函数本身是幂等的。 debouncedProcessAll(); return; // 一旦发现变化,即可退出循环并等待防抖函数执行 } } }); observer.observe(document.body, { childList: true, subtree: true }); } // 确保在 DOM 完全加载后执行 if (document.readyState === 'loading') { window.addEventListener('DOMContentLoaded', main); } else { main(); } })();