没有朋友

移除页面aff参数,增加代码复制按钮,并深度净化页面内容,移除各类推广元素。

// ==UserScript==
// @name         没有朋友
// @namespace    https://linux.do/u/amna/
// @version      3.0
// @description  移除页面aff参数,增加代码复制按钮,并深度净化页面内容,移除各类推广元素。
// @license      MIT
// @author       https://linux.do/u/amna/ (Refactored by Senior Software Engineer)
// @match        *://ygpy.net/*
// @icon         https://ygpy.net/images/logo-light.webp
// @run-at       document-start
// @grant        GM_setClipboard
// @grant        GM_addStyle
// ==/UserScript==

(function() {
    'use strict';

    // --- 全局常量和配置 ---
    const SCRIPT_BUTTON_CLASS = 'amna-copy-button';
    const DEBOUNCE_DELAY = 250; //ms

    const IGNORED_CODE_TEXT = new Set([
        "流媒体解锁", "多设备在线", "支持 BT 下载", "国内中转线路", "IEPL 专线路", "设备不限制",
        "晚高峰稳定", "国际专线", "IPLC 线路", "50+ 节点", "不限设备", "超大带宽", "赠送 Emby",
        "IEPL 专线", "家宽 IP", "厦门专线", "开始阅读", "仪表盘", "主页", "兑换礼品卡"
    ]);

    // --- 工具函数 ---
    function debounce(func, wait) {
        let timeout;
        return function(...args) {
            clearTimeout(timeout);
            timeout = setTimeout(() => func.apply(this, args), wait);
        };
    }

    /**
     * @description [架构优化] 注入CSS样式。现在包含更多规则,以在JS执行前消除闪屏。
     */
    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 .2s, background-color .2s; }

                /* === 抗闪屏 CSS 规则 === */
                /* 隐藏侧边栏最后一个组 */
                nav#VPSidebarNav > div.group:last-of-type { display: none !important; }

                /* 隐藏右侧辅助栏的广告容器 */
                div.VPDocAside > div.doc-aside-ads { display: none !important; }

                /* [新增] 使用:has()选择器隐藏目录中的广告,现代浏览器支持良好 */
                ul.VPDocOutlineItem li:has(a[title*="[AD]"]) { display: none !important; }

                div.vp-doc > div > div.vp-raw { display: none !important; }
            `);
            injected = true;
        };
    })();

    // --- 核心功能模块 ---

    function attachProactiveMenuListeners(container) {
        const menuButtons = container.querySelectorAll('.VPNavBarMenuGroup button:not([data-amna-menu-listener])');
        menuButtons.forEach(button => {
            button.setAttribute('data-amna-menu-listener', 'true');
            button.addEventListener('mouseover', () => {
                hideSponsoredMenuLinks(document.body);
            }, { passive: true });
        });
    }

    function hideSiblingsUntil(startElement, stopTagName) {
        let currentElement = startElement;
        while (currentElement) {
            currentElement.style.display = 'none';
            const nextElement = currentElement.nextElementSibling;
            if (!nextElement || nextElement.tagName === stopTagName) {
                break;
            }
            currentElement = nextElement;
        }
    }

    // [优化] 此函数不再需要,其功能已被CSS :has() 替代,但保留函数定义以防未来需要更复杂的JS逻辑
    function hideAdOutlineItems(container) {
        // Obsolete: Handled by CSS for anti-flicker.
    }

    function hideSponsoredSidebarItems(container) {
        const sidebarItems = container.querySelectorAll('section.VPSidebarItem > div.items > div');
        sidebarItems.forEach(itemDiv => {
            const pTag = itemDiv.querySelector('div > a > p');
            if (pTag && pTag.innerText.trim() === '付费机场') {
                itemDiv.remove();
            }
        });
    }

    function hideSponsoredContent(container) {
        const contentArea = container.querySelector('main.main > div.vp-doc > div');
        if (!contentArea) return;
        const h1 = contentArea.querySelector('h1');
        if (h1) {
            hideSiblingsUntil(h1, 'H2');
        }
        const adHeaders = contentArea.querySelectorAll('h2[id*="ad"]');
        adHeaders.forEach(h2 => {
            hideSiblingsUntil(h2, 'H2');
        });
        const children = Array.from(contentArea.children);
        const firstVisibleChild = children.find(el => el.style.display !== 'none');
        if (firstVisibleChild && firstVisibleChild.tagName === 'H2') {
            firstVisibleChild.style.marginTop = '0';
        }
    }

    function hideSponsoredHomeActions(container) {
        const actionDivs = container.querySelectorAll('div.VPHome div.main > div.actions > div.action');
        actionDivs.forEach(div => {
            const link = div.querySelector('a');
            if (link && link.innerText.trim() === '付费专栏') {
                div.remove();
            }
        });
    }

    function hideSponsoredMenuLinks(container) {
        const menuLinks = container.querySelectorAll('div.VPNavBarMenuGroup > div.menu div.items > div.VPMenuLink');
        menuLinks.forEach(div => {
            const span = div.querySelector('a > span');
            if (span && span.innerText.trim() === '付费机场') {
                div.style.display = 'none';
            }
        });
    }

    function cleanLinks(container) {
        const links = container.querySelectorAll('a[href*="?"]');
        links.forEach(link => {
            if (!link.href) return;
            try {
                const url = new URL(link.href);
                const hasSearchParams = url.search;
                const hasHashParams = url.hash && url.hash.includes('?');
                if (hasSearchParams || hasHashParams) {
                    let newHref = url.origin + url.pathname;
                    if (hasHashParams) {
                        newHref += url.hash.split('?')[0];
                    }
                    link.href = newHref;
                }
            } catch (error) {
                if (link.href.includes('?')) {
                    link.href = link.href.split('?')[0];
                }
            }
        });
    }

    function addCopyButtons(container) {
        const codeBlocks = container.querySelectorAll('code:not([data-amna-processed])');
        codeBlocks.forEach(code => {
            code.setAttribute('data-amna-processed', 'true');
            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);
            });
            code.after(copyButton);
        });
    }

    // --- 主执行逻辑与DOM监控 ---

    function processNode(node) {
        if (!(node instanceof Element)) return;
        const currentPath = window.location.pathname;
        if (currentPath.startsWith('/vpn/') && currentPath !== '/vpn/free.html') {
            hideSponsoredContent(node);
            // hideAdOutlineItems(node); // 已被CSS替代
        }
        attachProactiveMenuListeners(node);
        hideSponsoredSidebarItems(node);
        hideSponsoredHomeActions(node);
        hideSponsoredMenuLinks(node);
        cleanLinks(node);
        addCopyButtons(node);
    }

    const debouncedProcessAll = debounce(() => {
        console.log('[没有朋友] DOM发生变化,重新扫描页面...');
        processNode(document.body);
    }, DEBOUNCE_DELAY);

    // [架构优化] 主函数,现在它会在DOM加载后执行
    function main() {
        console.log('[没有朋友] 脚本核心逻辑启动 (v6.0)。');
        processNode(document.body);

        const observer = new MutationObserver((mutations) => {
            for (const mutation of mutations) {
                if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                    debouncedProcessAll();
                    return;
                }
            }
        });

        observer.observe(document.body, { childList: true, subtree: true });
    }

    // [架构优化] 脚本入口点
    // 1. 立即(在 document-start 时)注入CSS,这是消除闪屏的关键
    injectStyles();

    // 2. 等待DOM构建完成后,再执行依赖DOM的JS代码
    if (document.readyState === 'loading') {
        window.addEventListener('DOMContentLoaded', main);
    } else {
        main(); // 如果脚本被延迟注入,则直接执行
    }

})();