Greasy Fork

来自缓存

Greasy Fork is available in English.

论坛帖子批量选择小助手

在任何网页中多选帖子并批量打开,支持屏蔽词功能

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         论坛帖子批量选择小助手
// @namespace    http://tampermonkey.net/
// @version      1.6
// @description  在任何网页中多选帖子并批量打开,支持屏蔽词功能
// @author       黄萌萌可爱多
// @match        *://*/*
// @license      MIT
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    // 存储选中的帖子链接
    let selectedPosts = [];
    // 存储第一次选中的帖子索引
    let firstSelectedIndex = -1;
    // 存储最后一次选中的帖子索引
    let lastSelectedIndex = -1;

    // 获取屏蔽词列表
    let blacklist = GM_getValue('blacklist', '').split(' ').filter(Boolean);

    // 获取所有可点击的链接元素
    const clickableLinks = Array.from(document.querySelectorAll('a[href]'));

    // 创建已选择链接数量显示元素
    const selectedCountDisplay = document.createElement('div');
    selectedCountDisplay.style.position = 'fixed';
    selectedCountDisplay.style.top = '10px';
    selectedCountDisplay.style.right = '200px'; // 调整位置以避免与按钮重叠
    selectedCountDisplay.style.zIndex = '9999';
    selectedCountDisplay.style.padding = '10px 20px';
    selectedCountDisplay.style.backgroundColor = '#333';
    selectedCountDisplay.style.color = 'white';
    selectedCountDisplay.style.borderRadius = '5px';
    selectedCountDisplay.textContent = '已选择的链接:0'; // 初始化显示

    // 更新选中链接数量显示的函数
    function updateSelectedCountDisplay() {
        selectedCountDisplay.textContent = `已选择的链接:${selectedPosts.length}`;
    }

    // 添加点击事件监听
    clickableLinks.forEach((link, index) => {
        link.addEventListener('click', function(event) {
            const postLink = this.href; // 获取链接
            const linkText = this.textContent.trim();

            // 检查是否为时间格式(如 YYYY-MM-DD HH:MM)
            const isTimeFormat = /^\d{4}-\d{1,2}-\d{1,2} \d{1,2}:\d{2}$/.test(linkText);
            // 检查是否为纯数字、纯英文或数字加英文(可以包含符号,但不包含中文字符)
            const isPureNumericOrAlphanumeric = /^[a-zA-Z0-9\s\p{P}]*$/.test(linkText) && !/[\u4e00-\u9fa5]/u.test(linkText);
            // 检查文本长度是否少于个10字符
            const isShortText = linkText.length < 10; // 修改为不少于10字符
            // 修改为确保链接文本不包含任何一个屏蔽词
            const containsBlacklistWord = blacklist.some(word => linkText.includes(word));

            // 如果满足以下条件之一,则不选择该链接并应用屏蔽样式
            if ((isTimeFormat || isPureNumericOrAlphanumeric || isShortText || containsBlacklistWord) && (event.ctrlKey || event.shiftKey)) {
                this.classList.add('blacklisted'); // 添加屏蔽样式
                setTimeout(() => this.classList.remove('blacklisted'), 200); // 闪2次
                setTimeout(() => this.classList.add('blacklisted'), 400);
                setTimeout(() => this.classList.remove('blacklisted'), 600);

                // 如果链接符合屏蔽条件,阻止其在按住 Ctrl 或 Shift 键时被选中
                if (event.ctrlKey || event.shiftKey) {
                    event.preventDefault(); // 阻止默认的链接跳转行为
                    return; // 不进行任何操作,允许默认行为
                }
            }

            // 如果没有按下 Ctrl 或 Shift 键,并且没有选中的链接
            if (!event.ctrlKey && !event.shiftKey) {
                // 如果已经有选中的链接,并且再次点击任意一条被选择的链接
                if (selectedPosts.includes(postLink)) {
                    event.preventDefault(); // 阻止默认的链接跳转行为
                    selectedPosts.forEach(link => {
                        window.open(link, '_blank', 'noopener,noreferrer'); // 确保打开新标签页但不切换焦点且静默打开
                    });
                    selectedPosts = []; // 清空选中列表
                    clickableLinks.forEach(link => link.classList.remove('selected'));
                    firstSelectedIndex = -1; // 重置第一次选中的链接索引
                    lastSelectedIndex = -1; // 重置最后一次选中的链接索引
                } else {
                    // 允许用户点击并打开任何链接
                    return; // 不进行任何操作,允许默认行为
                }
            }

            // 检查是否按下了 Ctrl 键
            if (event.ctrlKey) {
                event.preventDefault(); // 阻止默认的链接跳转行为
                if (!selectedPosts.includes(postLink)) {
                    selectedPosts.push(postLink);
                    this.classList.add('selected'); // 添加选中样式
                } else {
                    selectedPosts = selectedPosts.filter(link => link !== postLink);
                    this.classList.remove('selected');
                }
            }
            // 检查是否按下了 Shift 键
            else if (event.shiftKey) {
                event.preventDefault(); // 阻止默认的链接跳转行为
                if (firstSelectedIndex === -1) {
                    // 如果是第一次按下 Shift 键,记录第一个选中的链接索引
                    firstSelectedIndex = index;
                }
                // 更新最后一个选中的链接索引
                lastSelectedIndex = index;

                // 确定选择范围
                const startIndex = Math.min(firstSelectedIndex, lastSelectedIndex);
                const endIndex = Math.max(firstSelectedIndex, lastSelectedIndex);

                // 清空之前的选中状态
                selectedPosts = [];
                clickableLinks.forEach(l => l.classList.remove('selected'));

                // 选择范围内的所有链接
                for (let i = startIndex; i <= endIndex; i++) {
                    const linkText = clickableLinks[i].textContent.trim();
                    const isTimeFormat = /^\d{4}-\d{1,2}-\d{1,2} \d{1,2}:\d{2}$/.test(linkText);
                    const isPureNumericOrAlphanumeric = /^[a-zA-Z0-9\s\p{P}]*$/.test(linkText) && !/[\u4e00-\u9fa5]/u.test(linkText);
                    const isShortText = linkText.length < 10;
                    const containsBlacklistWord = blacklist.some(word => linkText.includes(word));
                    if (!isTimeFormat && !isPureNumericOrAlphanumeric && !isShortText && !containsBlacklistWord) {
                        selectedPosts.push(clickableLinks[i].href);
                        clickableLinks[i].classList.add('selected');
                    }
                }
            }

            // 更新选中链接数量显示
            updateSelectedCountDisplay();

        });
    });

    // 监听点击页面非链接位置的事件
    document.addEventListener('click', function(event) {
        // 检查点击的是否是非链接位置
        if (!event.target.closest('a[href]')) {
            // 清空选中列表
            selectedPosts = [];
            clickableLinks.forEach(link => link.classList.remove('selected'));
            firstSelectedIndex = -1; // 重置第一次选中的链接索引
            lastSelectedIndex = -1; // 重置最后一次选中的链接索引

            // 更新选中链接数量显示
            updateSelectedCountDisplay();

        }
    });

    // 创建批量打开按钮
    const openButton = document.createElement('button');
    openButton.textContent = '批量打开选中的链接';
    openButton.style.position = 'fixed';
    openButton.style.top = '10px';
    openButton.style.right = '10px';
    openButton.style.zIndex = '9999';
    openButton.style.padding = '10px 20px';
    openButton.style.backgroundColor = '#4CAF50';
    openButton.style.color = 'white';
    openButton.style.border = 'none';
    openButton.style.borderRadius = '5px';
    openButton.style.cursor = 'pointer';
    openButton.style.boxShadow = '2px 2px 5px rgba(0, 0, 0, 0.3)';

    // 添加按钮点击事件
    openButton.addEventListener('click', function() {
        selectedPosts.forEach(link => {
            window.open(link, '_blank', 'noopener,noreferrer'); // 确保打开新标签页但不切换焦点且静默打开
        });
        selectedPosts = []; // 清空选中列表
        clickableLinks.forEach(link => link.classList.remove('selected'));
        firstSelectedIndex = -1; // 重置第一次选中的链接索引
        lastSelectedIndex = -1; // 重置最后一次选中的链接索引
        window.focus(); // 确保当前窗口保持焦点

        // 更新选中链接数量显示
        updateSelectedCountDisplay();

    });

    // 创建清除选择按钮
    const clearButton = document.createElement('button');
    clearButton.textContent = '清除选择';
    clearButton.style.position = 'fixed';
    clearButton.style.top = '50px';
    clearButton.style.right = '10px';
    clearButton.style.zIndex = '9999';
    clearButton.style.padding = '10px 20px';
    clearButton.style.backgroundColor = '#f44336';
    clearButton.style.color = 'white';
    clearButton.style.border = 'none';
    clearButton.style.borderRadius = '5px';
    clearButton.style.cursor = 'pointer';
    clearButton.style.boxShadow = '2px 2px 5px rgba(0, 0, 0, 0.3)';

    // 添加清除选择按钮点击事件
    clearButton.addEventListener('click', function() {
        selectedPosts = []; // 清空选中列表
        clickableLinks.forEach(link => link.classList.remove('selected'));
        firstSelectedIndex = -1; // 重置第一次选中的链接索引
        lastSelectedIndex = -1; // 重置最后一次选中的链接索引

        // 更新选中链接数量显示
        updateSelectedCountDisplay();

    });

    // 将按钮添加到页面
    document.body.appendChild(openButton);
    document.body.appendChild(clearButton);

    // 将已选择链接数量显示元素添加到页面
    document.body.appendChild(selectedCountDisplay);

    // 添加选中样式
    const style = document.createElement('style');
    style.textContent = `
        a.selected {
            background-color: #87CEEB; /* 修改选中颜色为天蓝色 */
        }
        a.blacklisted {
            background-color: #FF0000; /* 修改屏蔽颜色为红色 */
            animation: blink 1s infinite;
        }
        @keyframes blink {
            0%, 100% { opacity: 1; }
            50% { opacity: 0; }
        }
    `;
    document.head.appendChild(style);

    // 添加油猴设置菜单项
    GM_registerMenuCommand('⚙️ 设置', () => {
        // 创建设置弹窗
        const settingsBox = document.createElement('div');
        settingsBox.innerHTML = `
            <div style="position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 400px; height: 400px; background-color: #ffffff; border: 1px solid #ddd; z-index: 10000; padding: 20px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); border-radius: 12px; font-family: '-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif';">
                <div style="margin: 0 0 20px 0;">
                    <label style="margin-right: 10px; display: flex; align-items: center; font-size: 18px;">
                        <input type="checkbox" id="hideButtonsCheckbox" style="margin-right: 10px;" ${GM_getValue('hideButtons', false) ? 'checked' : ''}>
                        <span style="margin-left: 10px;">隐藏悬浮按钮</span>
                    </label>
                </div>
                <div style="margin-bottom: 20px;">
                    <label style="display: block; margin-bottom: 5px; font-size: 16px;">屏蔽词(用空格分隔):</label>
                    <input type="text" id="blacklistInput" style="width: 100%; padding: 8px; box-sizing: border-box;" value="${blacklist.join(' ')}">
                </div>
                <button id="saveBlacklistButton" style="margin-right: 10px; padding: 8px 16px; background-color: #4CAF50; color: white; border: none; border-radius: 12px; cursor: pointer; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);">保存屏蔽词</button>
                <ol style="margin: 0 0 20px 0; padding-left: 20px; list-style-type: decimal;">
                    <li style="margin-bottom: 10px;">第一次在某个网页使用批量打开功能时,需要手动关闭网页的阻止弹窗。</li>
                    <li style="margin-bottom: 10px;">按住 Ctrl 键并点击多个链接以多选链接。</li>
                    <li style="margin-bottom: 10px;">按住 Shift 键并点击链接以选择范围内的链接。</li>
                    <li style="margin-bottom: 10px;">当选择有链接时,再次点击任意一条链接即可全部打开。点击空白处即可取消选择全部链接。</li>
                    <li style="margin-bottom: 10px;">链接选择的逻辑参照 Windows 文件管理器。</li>
                </ol>
                <p style="margin-bottom: 10px; color: #555;">作者: 黄萌萌可爱多</p>
                <p style="margin-bottom: 10px; color: #555;">版本: 1.5.1</p>
                <button style="position: absolute; top: 10px; right: 10px; padding: 8px 16px; background-color: #f44336; color: white; border: none; border-radius: 12px; cursor: pointer; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);" onclick="this.parentElement.parentElement.remove()">关闭</button>
            </div>
        `;

        // 添加事件监听器
        const hideButtonsCheckbox = settingsBox.querySelector('#hideButtonsCheckbox');
        hideButtonsCheckbox.addEventListener('change', () => {
            GM_setValue('hideButtons', hideButtonsCheckbox.checked);
            openButton.style.display = hideButtonsCheckbox.checked ? 'none' : 'block';
            clearButton.style.display = hideButtonsCheckbox.checked ? 'none' : 'block';
            selectedCountDisplay.style.display = hideButtonsCheckbox.checked ? 'none' : 'block'; // 同步隐藏或显示“已选择的链接”数量显示元素
        });

        const saveBlacklistButton = settingsBox.querySelector('#saveBlacklistButton');
        saveBlacklistButton.addEventListener('click', () => {
            const newBlacklist = settingsBox.querySelector('#blacklistInput').value.split(' ').filter(Boolean);
            GM_setValue('blacklist', newBlacklist.join(' '));
            blacklist = newBlacklist;
            alert('屏蔽词已保存');
            settingsBox.remove();
        });

        // 将设置弹窗添加到页面
        document.body.appendChild(settingsBox);
    });

    // 检查并应用隐藏按钮的设置
    const hideButtons = GM_getValue('hideButtons', false);
    if (hideButtons) {
        openButton.style.display = 'none';
        clearButton.style.display = 'none';
        selectedCountDisplay.style.display = 'none'; // 同步隐藏“已选择的链接”数量显示元素
    }

})();