司机社助手

帮助自动回帖并提供磁力链接复制功能

// ==UserScript==
// @name         司机社助手
// @namespace    http://tampermonkey.net/
// @version      0.6
// @description  帮助自动回帖并提供磁力链接复制功能
// @author       91SM (优化: Claude)
// @match        https://xsijishe.com/*
// @match        https://xsijishe.net/*
// @match        https://sijishe.link/*
// @match        https://sijishe.info/*
// @match        https://sjs47.me/*
// @match        https://sj474.vip/*
// @match        https://sijishecn.cc/*
// @grant        none
// @require      https://code.jquery.com/jquery-3.6.0.min.js
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // 防止与页面上的jQuery冲突
    const $ = jQuery.noConflict();

    /**
     * 全局配置对象
     * @type {Object}
     * @property {string} buttonText - 显示在悬浮按钮上的文本
     * @property {string} replyMessage - 自动回复的默认消息
     * @property {number} cooldownTime - 回复冷却时间(秒)
     * @property {number} refreshDelay - 回复后刷新页面的延迟(毫秒)
     * @property {number} batchSize - DOM处理的批处理大小
     * @property {number} initDelay - 初始化延迟(毫秒)
     */
    const CONFIG = {
        buttonText: '快捷回复',
        replyMessage: '看了LZ的帖子,我只想说一句很好很强大!',
        cooldownTime: 30, // 回复冷却时间(秒)
        refreshDelay: 1000, // 回复后刷新页面的延迟(毫秒)
        batchSize: 5, // DOM处理的批处理大小
        initDelay: 300 // 初始化延迟(毫秒)
    };

    /**
     * DOM元素选择器 - 集中管理DOM选择器以提高可维护性
     * @type {Object}
     */
    const SELECTORS = {
        messageInput: "[name=message]",
        submitButton: "#fastpostsubmit",
        lockedText: ".locked",
        contentArea: ".t_f",
        jammerFont: "font.jammer"
    };

    /**
     * 本地存储键名
     * @type {Object}
     */
    const STORAGE_KEYS = {
        timestamp: "sj_timestamp",
        buttonPosition: "autoReplyButtonPosition"
    };

    /**
     * CSS类名和ID
     * @type {Object}
     */
    const CSS = {
        buttonId: "autoReplyButton",
        copyLink: "magnet-copy-link",
        notification: "magnet-copy-notif",
        notificationId: "magnet-copy-notif",
        showClass: "show"
    };

    /**
     * 按钮状态变量 - 用于拖拽功能
     * @type {Object}
     */
    let buttonState = {
        isDragging: false,
        startX: 0,
        startY: 0,
        startLeft: 0,
        startTop: 0
    };

    /**
     * 创建悬浮按钮
     * @returns {HTMLElement} 创建的按钮元素
     */
    function createFloatingButton() {
        // 创建按钮元素
        const button = document.createElement('button');
        button.textContent = CONFIG.buttonText;
        button.id = CSS.buttonId;

        // 设置按钮样式
        Object.assign(button.style, {
            position: 'fixed',
            padding: '10px 20px',
            backgroundColor: '#ffffff',
            color: '#000000',
            border: '1px solid #ccc',
            borderRadius: '4px',
            boxShadow: '0 2px 4px rgba(0,0,0,0.2)',
            cursor: 'move',
            zIndex: '9999',
            userSelect: 'none',
            pointerEvents: 'auto',
            fontSize: '14px',
            fontWeight: 'bold'
        });

        // 获取保存的位置或设置默认位置
        const savedPosition = getSavedPosition();
        if (savedPosition) {
            setButtonPosition(button, savedPosition.left, savedPosition.top);
        } else {
            setButtonPosition(button, window.innerWidth - 120, window.innerHeight / 2);
        }

        return button;
    }

    /**
     * 设置按钮位置,并确保在视口内
     * @param {HTMLElement} button - 按钮元素
     * @param {number} left - 左侧位置
     * @param {number} top - 顶部位置
     */
    function setButtonPosition(button, left, top) {
        // 获取视口尺寸
        const viewportWidth = window.innerWidth;
        const viewportHeight = window.innerHeight;

        // 获取或估计按钮尺寸
        const buttonWidth = button.offsetWidth || 100; // 估计宽度
        const buttonHeight = button.offsetHeight || 40; // 估计高度

        // 确保按钮在视口内
        left = Math.max(0, Math.min(left, viewportWidth - buttonWidth));
        top = Math.max(0, Math.min(top, viewportHeight - buttonHeight));

        // 设置按钮位置
        button.style.left = `${left}px`;
        button.style.top = `${top}px`;
    }

    /**
     * 保存按钮位置到localStorage
     * @param {number} left - 左侧位置
     * @param {number} top - 顶部位置
     */
    function saveButtonPosition(left, top) {
        localStorage.setItem(STORAGE_KEYS.buttonPosition, JSON.stringify({ left, top }));
    }

    /**
     * 获取保存的按钮位置
     * @returns {Object|null} 保存的位置对象或null
     */
    function getSavedPosition() {
        const savedPosition = localStorage.getItem(STORAGE_KEYS.buttonPosition);
        return savedPosition ? JSON.parse(savedPosition) : null;
    }

    /**
     * 处理自动回复逻辑
     */
    function handleAutoReply() {
        // 使用缓存的DOM选择器查询元素
        const $lockedText = $(SELECTORS.lockedText);

        // 检查是否有锁定文本元素
        if ($lockedText.length === 0) {
            alert("已经回复或此页面没有回复按钮!");
            return;
        }

        const lockedTextContent = $lockedText.text();

        // 检查是否需要购买主题
        if (lockedTextContent.includes('购买主题')) {
            alert("此页面需要手动点击购买!");
            return;
        }

        // 检查冷却时间
        const timestamp = localStorage.getItem(STORAGE_KEYS.timestamp);
        if (timestamp) {
            const currentTime = Date.now();
            const timeDifference = (currentTime - parseInt(timestamp)) / 1000;

            if (timeDifference < CONFIG.cooldownTime) {
                const remainingTime = Math.ceil(CONFIG.cooldownTime - timeDifference);
                alert(`距离上次自动回复还需等待${remainingTime}秒,请稍后再试`);
                return;
            }
        }

        // 执行回复
        if (lockedTextContent.includes('如果您要查看本帖隐藏内容请')) {
            // 使用缓存的选择器填写回复内容并提交
            $(SELECTORS.messageInput).val(CONFIG.replyMessage);
            $(SELECTORS.submitButton).click();

            // 更新时间戳
            localStorage.setItem(STORAGE_KEYS.timestamp, Date.now().toString());

            // 延迟后刷新页面
            setTimeout(() => {
                window.scrollTo(0, 0);
                location.reload();
            }, CONFIG.refreshDelay);
        }
    }

    /**
     * 为按钮添加拖拽功能
     * @param {HTMLElement} button - 按钮元素
     */
    function addDragFunctionality(button) {
        // 鼠标按下事件
        button.addEventListener('mousedown', function(e) {
            if (e.button !== 0) return; // 只响应左键
            e.stopPropagation();

            buttonState.isDragging = true;
            buttonState.startX = e.clientX;
            buttonState.startY = e.clientY;
            buttonState.startLeft = parseInt(button.style.left) || 0;
            buttonState.startTop = parseInt(button.style.top) || 0;
        });

        // 鼠标移动事件(添加到document以支持拖出按钮范围)
        document.addEventListener('mousemove', function(e) {
            if (!buttonState.isDragging) return;
            e.preventDefault();

            const newLeft = buttonState.startLeft + e.clientX - buttonState.startX;
            const newTop = buttonState.startTop + e.clientY - buttonState.startY;

            setButtonPosition(button, newLeft, newTop);
            saveButtonPosition(newLeft, newTop);
        });

        // 鼠标松开事件
        document.addEventListener('mouseup', function() {
            buttonState.isDragging = false;
        });
    }

    /**
     * 初始化脚本主功能
     */
    function initScript() {
        // 创建按钮
        const button = createFloatingButton();

        // 添加拖拽功能
        addDragFunctionality(button);

        // 添加双击回复功能
        button.addEventListener('dblclick', function(e) {
            e.stopPropagation();
            if (!buttonState.isDragging) {
                handleAutoReply();
            }
        });

        // 添加按钮到页面
        document.body.appendChild(button);
    }

    /**
     * RequestIdleCallback的兼容性实现
     * 用于在浏览器空闲时执行非关键操作
     */
    const requestIdleCallback = window.requestIdleCallback ||
                               function(cb) {
                                   const start = Date.now();
                                   return setTimeout(function() {
                                       cb({
                                           didTimeout: false,
                                           timeRemaining: function() {
                                               return Math.max(0, 50 - (Date.now() - start));
                                           }
                                       });
                                   }, 1);
                               };

    /**
     * 注入CSS样式
     * 使用一次性注入而不是内联样式,提高性能
     */
    function injectStyles() {
        const style = document.createElement('style');
        style.textContent = `
            .${CSS.copyLink} {
                color: #1e88e5;
                margin-left: 8px;
                cursor: pointer;
                text-decoration: underline;
                font-size: 12px;
                display: inline-block;
            }
            .${CSS.notification} {
                position: fixed;
                top: 20px;
                right: 20px;
                background: rgba(76, 175, 80, 0.9);
                color: white;
                padding: 8px 16px;
                border-radius: 4px;
                z-index: 9999;
                font-size: 14px;
                opacity: 0;
                pointer-events: none;
                transition: opacity 0.2s;
            }
            .${CSS.notification}.${CSS.showClass} {
                opacity: 1;
            }
        `;
        document.head.appendChild(style);
    }

    /**
     * 复制文本到剪贴板
     * @param {string} text - 要复制的文本
     */
    function copyText(text) {
        // 获取或创建通知元素
        let notif = document.getElementById(CSS.notificationId);
        if (!notif) {
            notif = document.createElement('div');
            notif.id = CSS.notificationId;
            notif.className = CSS.notification;
            notif.textContent = '磁力链接已复制!';
            document.body.appendChild(notif);
        }

        // 使用最有效的方法复制到剪贴板
        const copyToClipboard = () => {
            try {
                // 尝试使用现代API
                navigator.clipboard.writeText(text);
            } catch(e) {
                // 回退到传统方法
                const textarea = document.createElement('textarea');
                textarea.value = text;
                textarea.style.position = 'fixed';
                textarea.style.opacity = '0';
                document.body.appendChild(textarea);
                textarea.focus();
                textarea.select();
                document.execCommand('copy');
                document.body.removeChild(textarea);
            }

            // 显示通知
            notif.classList.add(CSS.showClass);
            setTimeout(() => notif.classList.remove(CSS.showClass), 1500);
        };

        copyToClipboard();
    }

    /**
     * 处理一批元素
     * 使用分批处理避免页面卡顿
     * @param {NodeList} elements - 要处理的元素列表
     * @param {number} index - 当前处理索引
     * @param {number} batchSize - 每批处理的元素数量
     * @param {Function} callback - 处理完成后的回调函数
     */
    function processBatch(elements, index, batchSize, callback) {
        const endIndex = Math.min(index + batchSize, elements.length);

        requestIdleCallback(function(deadline) {
            // 创建磁力链接正则表达式(只创建一次)
            const magnetRegex = /magnet:\?xt=urn:btih:[A-Z0-9]+/g;

            while (index < endIndex && (deadline.timeRemaining() > 0 || deadline.didTimeout)) {
                const element = elements[index++];

                // 使用更高效的方式选择和处理干扰元素
                const jammers = element.querySelectorAll(SELECTORS.jammerFont);
                if (jammers.length > 0) {
                    jammers.forEach(j => j.remove());
                }

                // 处理磁力链接
                const text = element.innerHTML;

                // 只有在包含磁力链接时才执行替换,提高性能
                if (magnetRegex.test(text)) {
                    element.innerHTML = text.replace(magnetRegex, function(match) {
                        return match + `<span class="${CSS.copyLink}" data-magnet="${match}">复制</span>`;
                    });
                }
            }

            // 继续处理下一批或完成
            if (index < elements.length) {
                processBatch(elements, index, batchSize, callback);
            } else if (callback) {
                callback();
            }
        });
    }

    /**
     * 初始化磁力链接处理功能
     * 使用事件委托提高性能
     */
    function initMagnetLinkHandler() {
        // 使用事件委托处理复制链接点击
        document.addEventListener('click', function(e) {
            // 检查是否点击了复制链接
            if (e.target && e.target.classList.contains(CSS.copyLink)) {
                const magnetLink = e.target.getAttribute('data-magnet');
                copyText(magnetLink);

                // 更新链接文本提供反馈
                e.target.textContent = '已复制!';
                setTimeout(() => {
                    e.target.textContent = '复制';
                }, 1000);

                e.preventDefault();
            }
        }, false);
    }

    /**
     * 初始化磁力链接功能
     * 使用优化的DOM查询和处理
     */
    function initMagnetFeature() {
        // 一次性查找内容元素,使用缓存的选择器
        const tfElements = document.querySelectorAll(SELECTORS.contentArea);
        if (!tfElements.length) return;

        // 先注入样式
        injectStyles();

        // 设置事件处理器
        initMagnetLinkHandler();

        // 使用批处理处理元素,防止UI卡顿
        processBatch(tfElements, 0, CONFIG.batchSize, null);
    }

    /**
     * 主初始化函数
     * 根据页面状态初始化功能
     */
    function init() {
        // 初始化自动回复功能
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', initScript);
        } else {
            initScript();
        }

        // 延迟初始化磁力链接功能,优先渲染页面
        setTimeout(initMagnetFeature, CONFIG.initDelay);
    }

    // 执行初始化
    init();
})();