Greasy Fork

Greasy Fork is available in English.

知识星球吖

1. 去掉 disabled-copy 类及水印遮挡;2. 自动点击"展开"按钮保持全文展开;3. 点击时间复制帖子内容;4. 每3分钟自动刷新(可开关);5. 界面美化和侧边栏控制

当前为 2025-10-29 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         知识星球吖
// @namespace    https://axutongxue.com/
// @version      2.8
// @license      MIT
// @description  1. 去掉 disabled-copy 类及水印遮挡;2. 自动点击"展开"按钮保持全文展开;3. 点击时间复制帖子内容;4. 每3分钟自动刷新(可开关);5. 界面美化和侧边栏控制
// @author       kejin——公众号:懒人股选
// @match        *://wx.zsxq.com/*
// @grant        GM_addStyle
// ==/UserScript==
(function () {
    'use strict';

    /* ************** 样式注入 ************** */
    // 添加自定义样式
    if (typeof GM_addStyle !== 'undefined') {
        GM_addStyle(`
            .group-list-container {
                transition: transform 0.2s ease-out;
                background: #fff;
                z-index: 10;
            }
            #toggle-sidebar {
                height: 20px;
                font-size: 12px;
                margin-left: 10px;
                padding: 0px 6px;
                border-radius: 4px;
                cursor: pointer;
                color: rgb(80, 234, 203);
                background-color: rgba(52, 146, 112, 0.05);
                border: 1px solid rgba(65,183,140,.2);
            }
            #toggle-sidebar:hover {
                background-color: #e0e0e0;
            }
            #auto-refresh-control {
                position: fixed;
                top: 10px;
                right: 10px;
                background: white;
                padding: 6px 10px;
                border-radius: 6px;
                box-shadow: 0 2px 8px rgba(0,0,0,0.1);
                z-index: 9999;
                display: flex;
                align-items: center;
                gap: 8px;
                font-size: 13px;
            }
            .control-item {
                display: flex;
                align-items: center;
                gap: 5px;
            }
            .control-divider {
                width: 1px;
                height: 16px;
                background: #e0e0e0;
            }
            #toggle-sidebar-btn {
                height: 24px;
                font-size: 12px;
                padding: 0px 10px;
                border-radius: 4px;
                cursor: pointer;
                color: white;
                background-color: #50EACB;
                border: none;
                transition: background-color 0.2s;
            }
            #toggle-sidebar-btn:hover {
                background-color: #3DD4B5;
            }
            #auto-refresh-toggle.active {
                background: #EF97AF;
            }
            #refresh-countdown {
                color: #666;
                font-size: 12px;
                min-width: 40px;
                text-align: right;
            }
            #auto-refresh-toggle.active {
                background: #EF97AF;
            }
            #refresh-countdown {
                color: #666;
                font-size: 12px;
                min-width: 45px;
                text-align: right;
            }
            #auto-refresh-toggle {
                position: relative;
                width: 40px;
                height: 20px;
                background: #ccc;
                border-radius: 10px;
                cursor: pointer;
                transition: background 0.3s;
            }
            #auto-refresh-toggle.active {
                background: rgb(80, 234, 203);
            }
            #auto-refresh-toggle::after {
                content: '';
                position: absolute;
                top: 2px;
                left: 2px;
                width: 16px;
                height: 16px;
                background: white;
                border-radius: 50%;
                transition: transform 0.3s;
            }
            #auto-refresh-toggle.active::after {
                transform: translateX(20px);
            }
            #refresh-countdown {
                color: #666;
                font-size: 12px;
                min-width: 80px;
            }
        `);
    } else {
        // 如果 GM_addStyle 不可用,使用传统方式
        const style = document.createElement('style');
        style.textContent = `
            .group-list-container {
                transition: transform 0.2s ease-out;
                background: #fff;
                z-index: 10;
            }
            #toggle-sidebar {
                height: 20px;
                font-size: 12px;
                margin-left: 10px;
                padding: 0px 6px;
                border-radius: 4px;
                cursor: pointer;
                color: rgb(80, 234, 203);
                background-color: rgba(52, 146, 112, 0.05);
                border: 1px solid rgba(65,183,140,.2);
            }
            #toggle-sidebar:hover {
                background-color: #e0e0e0;
            }
            #auto-refresh-control {
                position: fixed;
                top: 10px;
                right: 10px;
                background: white;
                padding: 10px 15px;
                border-radius: 6px;
                box-shadow: 0 2px 8px rgba(0,0,0,0.1);
                z-index: 9999;
                display: flex;
                align-items: center;
                gap: 15px;
                font-size: 13px;
            }
            .control-item {
                display: flex;
                align-items: center;
                gap: 8px;
            }
            .control-divider {
                width: 1px;
                height: 20px;
                background: #e0e0e0;
            }
            #toggle-sidebar-btn {
                height: 24px;
                font-size: 12px;
                padding: 0px 10px;
                border-radius: 4px;
                cursor: pointer;
                color: rgb(80, 234, 203);
                background-color: rgba(52, 146, 112, 0.05);
                border: 1px solid rgba(65,183,140,.2);
                transition: background-color 0.2s;
            }
            #toggle-sidebar-btn:hover {
                background-color: rgba(52, 146, 112, 0.1);
            }
            #auto-refresh-toggle {
                position: relative;
                width: 40px;
                height: 20px;
                background: #ccc;
                border-radius: 10px;
                cursor: pointer;
                transition: background 0.3s;
            }
            #auto-refresh-toggle.active {
                background: rgb(80, 234, 203);
            }
            #auto-refresh-toggle::after {
                content: '';
                position: absolute;
                top: 2px;
                left: 2px;
                width: 16px;
                height: 16px;
                background: white;
                border-radius: 50%;
                transition: transform 0.3s;
            }
            #auto-refresh-toggle.active::after {
                transform: translateX(20px);
            }
            #refresh-countdown {
                color: #666;
                font-size: 12px;
                min-width: 80px;
            }
        `;
        document.head.appendChild(style);
    }

    /* ************** 通用工具 ************** */
    function waitForKeyElements(selectorOrFunction, callback, waitOnce = true, interval = 300, maxIntervals = -1) {
        const select = () => (typeof selectorOrFunction === 'function' ? selectorOrFunction() : document.querySelectorAll(selectorOrFunction));
        const tick = () => {
            const nodes = select();
            if (nodes.length) {
                nodes.forEach(n => {
                    if (n.dataset.alreadyFound) return;
                    const cancel = callback(n);
                    if (!cancel) n.dataset.alreadyFound = '1';
                });
            }
            if (--maxIntervals !== 0 && !(waitOnce && nodes.length)) {
                setTimeout(tick, interval);
            }
        };
        tick();
    }

    /* ************** 1. 解除复制限制 ************** */
    waitForKeyElements('.disabled-copy', el => el.classList.remove('disabled-copy'), false, 1000);
    waitForKeyElements('[watermark]', el => el.setAttribute('style', 'padding:10px;'), false, 1000);

    /* ************** 2. 帖子自动展开 ************** */
    const processed = new WeakSet();
    function smartClick(btn) {
        if (!btn || processed.has(btn)) return;
        const txt = btn.textContent.trim();
        if (!/展[开示]/.test(txt)) return;

        // 尝试多种点击方式
        try {
            btn.click(); // 原生点击
        } catch (e) {
            btn.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }));
        }
        processed.add(btn);
    }

    function expandAll() {
        // 扩展选择器,覆盖更多可能的展开按钮
        const selectors = [
            'p.showAll',
            'button.showAll',
            'span.showAll',
            '[class*="showAll"]',
            '[class*="show-all"]',
            '[class*="展开"]',
            'button:not([data-expanded])',
            'span:not([data-expanded])',
            'p:not([data-expanded])'
        ];

        selectors.forEach(selector => {
            document.querySelectorAll(selector).forEach(el => {
                const text = el.textContent.trim();
                if (/展[开示]|show\s*all|show\s*more/i.test(text)) {
                    smartClick(el);
                }
            });
        });
    }

    // 首次+动态
    let expandObserver = null;
    window.addEventListener('load', () => {
        // 立即执行一次
        expandAll();

        // 延迟执行确保DOM完全加载
        setTimeout(expandAll, 500);
        setTimeout(expandAll, 1000);
        setTimeout(expandAll, 2000);

        // 监听DOM变化
        if (expandObserver) expandObserver.disconnect();
        expandObserver = new MutationObserver(() => {
            expandAll();
        });
        expandObserver.observe(document.body, {
            childList: true,
            subtree: true,
            attributes: false
        });

        window.addEventListener('hashchange', expandAll);

        // 滚动时也尝试展开
        let scrollTimer = null;
        window.addEventListener('scroll', () => {
            if (scrollTimer) clearTimeout(scrollTimer);
            scrollTimer = setTimeout(expandAll, 300);
        }, { passive: true });
    });

    /* ************** 3. 点击时间复制帖子内容 ************** */
    const timeClickProcessed = new WeakSet();

    function findPostContent(timeElement) {
        // 向上查找帖子容器
        let container = timeElement.closest('[class*="topic"], [class*="post"], [class*="feed-item"]');
        if (!container) {
            container = timeElement.closest('div[class*="item"]');
        }

        if (!container) return null;

        // 查找内容区域(尝试多种可能的选择器)
        const contentSelectors = [
            '.content',
            '[class*="content"]',
            '.text',
            '[class*="text"]',
            '.description',
            '[class*="description"]'
        ];

        for (const selector of contentSelectors) {
            const content = container.querySelector(selector);
            if (content && content.textContent.trim()) {
                return content.textContent.trim();
            }
        }

        return null;
    }

    function copyToClipboard(text) {
        if (navigator.clipboard && navigator.clipboard.writeText) {
            navigator.clipboard.writeText(text).then(() => {
                showCopyTip('复制成功!');
            }).catch(() => {
                fallbackCopy(text);
            });
        } else {
            fallbackCopy(text);
        }
    }

    function fallbackCopy(text) {
        const textarea = document.createElement('textarea');
        textarea.value = text;
        textarea.style.position = 'fixed';
        textarea.style.opacity = '0';
        document.body.appendChild(textarea);
        textarea.select();
        try {
            document.execCommand('copy');
            showCopyTip('复制成功!');
        } catch (err) {
            showCopyTip('复制失败,请手动复制');
        }
        document.body.removeChild(textarea);
    }

    function showCopyTip(message) {
        const tip = document.createElement('div');
        tip.textContent = message;
        tip.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(0, 0, 0, 0.8);
            color: white;
            padding: 12px 24px;
            border-radius: 6px;
            font-size: 14px;
            z-index: 10000;
            pointer-events: none;
        `;
        document.body.appendChild(tip);
        setTimeout(() => tip.remove(), 2000);
    }

    function addTimeClickListener(timeElement) {
        if (timeClickProcessed.has(timeElement)) return;

        // 添加样式提示可点击
        timeElement.style.cursor = 'pointer';
        timeElement.title = '点击复制帖子内容';

        timeElement.addEventListener('click', (e) => {
            e.preventDefault();
            e.stopPropagation();

            const content = findPostContent(timeElement);
            if (content) {
                copyToClipboard(content);
            } else {
                showCopyTip('未找到帖子内容');
            }
        });

        timeClickProcessed.add(timeElement);
    }

    // 监听时间元素(尝试多种可能的选择器)
    function attachTimeListeners() {
        const timeSelectors = [
            '[class*="time"]',
            '[class*="date"]',
            'time',
            'span[title*="202"]', // 匹配包含年份的时间
        ];

        timeSelectors.forEach(selector => {
            waitForKeyElements(selector, (el) => {
                // 检查是否是时间格式
                const text = el.textContent.trim();
                if (/\d{4}-\d{2}-\d{2}/.test(text) || /\d{2}:\d{2}/.test(text)) {
                    addTimeClickListener(el);
                }
            }, false, 1000);
        });
    }

    // 启动时间点击监听
    window.addEventListener('load', () => {
        attachTimeListeners();
    });

    /* ************** 4. 自动刷新功能(3分钟) ************** */
    let refreshTimer = null;
    let countdownInterval = null;
    let isAutoRefreshEnabled = localStorage.getItem('autoRefreshEnabled') !== 'false'; // 默认开启
    const REFRESH_INTERVAL = 180000; // 3分钟 = 180秒 = 180000毫秒
    let remainingTime = REFRESH_INTERVAL;

    // 创建控制面板
    function createRefreshControl() {
        const controlDiv = document.createElement('div');
        controlDiv.id = 'auto-refresh-control';
        controlDiv.innerHTML = `
            <button id="toggle-sidebar-btn">切换目录</button>
            <div class="control-divider"></div>
            <div class="control-item">
                <span>自动刷新</span>
                <div id="auto-refresh-toggle" class="${isAutoRefreshEnabled ? 'active' : ''}"></div>
                <span id="refresh-countdown"></span>
            </div>
        `;
        document.body.appendChild(controlDiv);

        const toggle = document.getElementById('auto-refresh-toggle');
        const countdown = document.getElementById('refresh-countdown');

        // 切换开关
        toggle.addEventListener('click', () => {
            isAutoRefreshEnabled = !isAutoRefreshEnabled;
            localStorage.setItem('autoRefreshEnabled', isAutoRefreshEnabled);
            toggle.classList.toggle('active');

            if (isAutoRefreshEnabled) {
                startAutoRefresh();
                console.log('自动刷新已启用:每3分钟刷新一次');
            } else {
                stopAutoRefresh();
                countdown.textContent = '已禁用';
                console.log('自动刷新已禁用');
            }
        });

        return countdown;
    }

    // 格式化时间显示
    function formatTime(ms) {
        const totalSeconds = Math.floor(ms / 1000);
        const minutes = Math.floor(totalSeconds / 60);
        const seconds = totalSeconds % 60;
        return `${minutes}:${seconds.toString().padStart(2, '0')}`;
    }

    // 更新倒计时显示
    function updateCountdown(countdownElement) {
        if (!isAutoRefreshEnabled) {
            countdownElement.textContent = '已禁用';
            return;
        }
        countdownElement.textContent = formatTime(remainingTime);
    }

    // 启动自动刷新
    function startAutoRefresh() {
        stopAutoRefresh(); // 先清除旧的定时器

        remainingTime = REFRESH_INTERVAL;
        const countdownElement = document.getElementById('refresh-countdown');

        // 更新倒计时(每秒更新一次)
        countdownInterval = setInterval(() => {
            remainingTime -= 1000;
            if (remainingTime <= 0) {
                remainingTime = 0;
            }
            updateCountdown(countdownElement);
        }, 1000);

        // 设置刷新定时器
        refreshTimer = setTimeout(() => {
            console.log('3分钟到,刷新页面');
            location.reload();
        }, REFRESH_INTERVAL);

        updateCountdown(countdownElement);
    }

    // 停止自动刷新
    function stopAutoRefresh() {
        if (refreshTimer) {
            clearTimeout(refreshTimer);
            refreshTimer = null;
        }
        if (countdownInterval) {
            clearInterval(countdownInterval);
            countdownInterval = null;
        }
    }

    // 页面加载后初始化
    window.addEventListener('load', () => {
        setTimeout(() => {
            const countdownElement = createRefreshControl();

            if (isAutoRefreshEnabled) {
                startAutoRefresh();
                console.log('自动刷新功能已启动:每3分钟刷新一次');
            } else {
                updateCountdown(countdownElement);
                console.log('自动刷新功能已禁用(可通过右上角开关启用)');
            }
        }, 500);
    });

    /* ************** 5. 界面优化功能 ************** */

    // 5.1 为精华帖添加红色边框
    function addRedBorder() {
        const listItems = document.querySelectorAll('.ng-star-inserted');
        listItems.forEach((item) => {
            const headerContainer = item.querySelector('.header-container');
            if (headerContainer) {
                const topicFlag = headerContainer.querySelector('.topic-flag');
                if (topicFlag) {
                    const digestElement = topicFlag.querySelector('.digest');
                    if (digestElement) {
                        const topicContainer = item.querySelector('.topic-container');
                        if (topicContainer) {
                            topicContainer.style.border = '1px solid rgb(80, 234, 203)';
                            topicContainer.style.backgroundColor = 'rgba(80, 234, 203, 0.1)';
                        }
                    }
                }
            }
        });
    }

    // 5.2 设置侧边栏切换功能
    function setupSidebarToggle() {
        const sidebar = document.querySelector('.group-list-container');
        if (!sidebar) return;

        // 获取视口宽度
        const clientWidth = document.documentElement.clientWidth;
        const transX = clientWidth - 1526 / 2;

        // 初始化侧边栏状态(从localStorage读取,默认显示)
        const isHidden = localStorage.getItem('sidebarHidden') === 'true';
        if (isHidden) {
            sidebar.classList.add('hide');
            sidebar.style.transform = 'translateX(calc(-100% - ' + transX + 'px))';
        } else {
            sidebar.classList.remove('hide');
            sidebar.style.transform = 'translateX(0)';
        }

        // 绑定到右上角按钮
        const toggleButton = document.getElementById('toggle-sidebar-btn');
        if (toggleButton && !toggleButton.dataset.bound) {
            toggleButton.dataset.bound = 'true';
            toggleButton.addEventListener('click', () => {
                sidebar.classList.toggle('hide');

                if (sidebar.classList.contains('hide')) {
                    sidebar.style.transform = 'translateX(calc(-100% - ' + transX + 'px))';
                    localStorage.setItem('sidebarHidden', 'true');
                } else {
                    sidebar.style.transform = 'translateX(0)';
                    localStorage.setItem('sidebarHidden', 'false');
                }
            });
        }
    }

    // 5.3 隐藏不必要的界面元素
    function hideElements() {
        // 头部
        const headerContainer = document.querySelector('.header-container');

        if (headerContainer) {
            const redirect = headerContainer.querySelector('.redirect');
            const userAvatar = headerContainer.querySelector('.user-avatar');

            if (redirect) {
                redirect.style.display = 'none';
            }
            if (userAvatar) {
                userAvatar.style.display = 'none';
            }
        }

        const leftLogo = document.querySelector('.logo-container .left');
        const noteLogo = document.querySelector('.logo-container .note');
        if (leftLogo) {
            leftLogo.style.display = 'none';
        }
        if (noteLogo) {
            noteLogo.style.display = 'none';
        }

        // .topic-flow-container
        const topicFlowContainer = document.querySelector('.topic-flow-container');
        if (topicFlowContainer) {
            topicFlowContainer.style.setProperty('width', '100%', 'important');
            topicFlowContainer.style.setProperty('padding-left', '100px', 'important');
            topicFlowContainer.style.setProperty('padding-right', '100px', 'important');
        }

        // 主体上部分
        const topicContainer = document.querySelector('.topic-container');
        if (topicContainer) {
            const ngStarInserted = topicContainer.querySelector('.ng-star-inserted');
            if (ngStarInserted) {
                ngStarInserted.style.display = 'none';
            }
        }

        // 主体中部分
        const mainContentContainer = document.querySelector('.main-content-container');
        if (mainContentContainer) {
            mainContentContainer.style.setProperty('margin-left', '10%', 'important');
            mainContentContainer.style.setProperty('margin-right', '10%', 'important');
        }

        // 右侧边栏
        const groupPreviewContainer = document.querySelector('.group-preview-container');
        if (groupPreviewContainer) {
            groupPreviewContainer.style.display = 'none';
        }
    }

    // 5.4 启动界面优化功能
    function initUIEnhancements() {
        addRedBorder();
        setupSidebarToggle();
        hideElements();

        // 监听DOM变化,持续应用样式
        const observer = new MutationObserver(() => {
            addRedBorder();
            setupSidebarToggle();
            hideElements();
        });

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

    // 页面加载完成后执行界面优化
    window.addEventListener('load', () => {
        setTimeout(initUIEnhancements, 500);
    });
})();