Smogon自动点赞工具 v1.2

自动点赞Smogon用户的历史发帖,支持自动翻到older results,移除返回结果检查,增强错误调试功能

// ==UserScript==
// @name         Smogon自动点赞工具 v1.2
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  自动点赞Smogon用户的历史发帖,支持自动翻到older results,移除返回结果检查,增强错误调试功能
// @author       Xuwu
// @match        https://www.smogon.com/forums/*
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_notification
// @grant        GM_log
// @connect      smogon.com
// @require      https://code.jquery.com/jquery-3.6.0.min.js
// ==/UserScript==

(function() {
    'use strict';

    // 配置
    const config = {
        minDelay: 5000, // 最小延迟(毫秒)
        maxDelay: 10000, // 最大延迟(毫秒)
        timeout: 15000, // 请求超时时间(毫秒)
        debugMode: true // 启用调试模式
    };

    // 创建UI
    function createUI() {
        // 主容器
        const container = $('<div>', {
            id: 'smogon-auto-liker',
            css: {
                position: 'fixed',
                top: '20px',
                right: '20px',
                background: '#fff',
                border: '2px solid #3498db',
                borderRadius: '8px',
                padding: '15px',
                zIndex: '9999',
                boxShadow: '0 4px 8px rgba(0,0,0,0.2)',
                minWidth: '300px',
                fontFamily: 'Arial, sans-serif'
            }
        });

        // 标题
        const title = $('<h3>', {
            text: 'Smogon自动点赞工具 v1.2',
            css: {
                margin: '0 0 15px 0',
                color: '#3498db',
                borderBottom: '1px solid #eee',
                paddingBottom: '10px'
            }
        });

        // URL输入
        const urlLabel = $('<label>', {
            text: '用户发帖历史URL:',
            css: {
                display: 'block',
                marginBottom: '5px',
                fontWeight: 'bold'
            }
        });

        const urlInput = $('<input>', {
            type: 'text',
            id: 'smogon-user-url',
            placeholder: '例如: https://www.smogon.com/forums/search/63660517/?c[users]=xuwu&o=date',
            css: {
                width: '100%',
                padding: '8px',
                marginBottom: '15px',
                boxSizing: 'border-box',
                border: '1px solid #ddd',
                borderRadius: '4px'
            }
        });

        // 页数输入
        const pagesLabel = $('<label>', {
            text: '要处理的页数:',
            css: {
                display: 'block',
                marginBottom: '5px',
                fontWeight: 'bold'
            }
        });

        const pagesInput = $('<input>', {
            type: 'number',
            id: 'smogon-pages',
            value: '1',
            min: '1',
            max: '50',
            css: {
                width: '100%',
                padding: '8px',
                marginBottom: '15px',
                boxSizing: 'border-box',
                border: '1px solid #ddd',
                borderRadius: '4px'
            }
        });

        // 调试模式开关
        const debugLabel = $('<label>', {
            text: '调试模式:',
            css: {
                display: 'block',
                marginBottom: '5px',
                fontWeight: 'bold'
            }
        });

        const debugCheckbox = $('<input>', {
            type: 'checkbox',
            id: 'smogon-debug',
            checked: config.debugMode,
            css: {
                marginRight: '5px'
            }
        }).change(function() {
            config.debugMode = this.checked;
            GM_setValue('debug_mode', this.checked);
        });

        const debugContainer = $('<div>', {
            css: {
                marginBottom: '15px'
            }
        }).append(debugCheckbox, $('<span>', {text: '启用详细调试信息'}));

        // 按钮容器
        const buttonContainer = $('<div>', {
            css: {
                display: 'flex',
                justifyContent: 'space-between'
            }
        });

        // 开始按钮
        const startButton = $('<button>', {
            id: 'smogon-start',
            text: '开始点赞',
            css: {
                padding: '10px 15px',
                background: '#2ecc71',
                color: 'white',
                border: 'none',
                borderRadius: '4px',
                cursor: 'pointer',
                fontWeight: 'bold',
                flex: '1',
                marginRight: '5px'
            }
        });

        // 停止按钮
        const stopButton = $('<button>', {
            id: 'smogon-stop',
            text: '停止',
            css: {
                padding: '10px 15px',
                background: '#e74c3c',
                color: 'white',
                border: 'none',
                borderRadius: '4px',
                cursor: 'pointer',
                fontWeight: 'bold',
                flex: '1',
                marginLeft: '5px',
                display: 'none'
            }
        });

        // 状态显示
        const status = $('<div>', {
            id: 'smogon-status',
            text: '等待开始...',
            css: {
                marginTop: '15px',
                padding: '10px',
                background: '#f9f9f9',
                borderRadius: '4px',
                fontSize: '14px',
                maxHeight: '150px',
                overflowY: 'auto'
            }
        });

        // 组装UI
        buttonContainer.append(startButton, stopButton);
        container.append(
            title,
            urlLabel,
            urlInput,
            pagesLabel,
            pagesInput,
            debugLabel,
            debugContainer,
            buttonContainer,
            status
        );

        // 添加到页面
        $('body').append(container);

        // 保存上次使用的URL和调试模式设置
        const lastUrl = GM_getValue('last_user_url', '');
        const debugMode = GM_getValue('debug_mode', config.debugMode);

        if (lastUrl) {
            urlInput.val(lastUrl);
        }
        debugCheckbox.prop('checked', debugMode);
        config.debugMode = debugMode;

        // 事件处理
        startButton.click(startLiking);
        stopButton.click(stopLiking);
    }

    // 状态变量
    let isRunning = false;
    let currentPage = 1;
    let totalPages = 1;
    let postIds = [];
    let processedPosts = 0;
    let baseUrl = ''; // 当前base URL,支持翻到older results后更新

    // 开始点赞
    function startLiking() {
        if (isRunning) return;

        baseUrl = $('#smogon-user-url').val().trim();
        if (!baseUrl) {
            updateStatus('错误: 请输入用户发帖历史URL', 'error');
            return;
        }

        // 保存URL供下次使用
        GM_setValue('last_user_url', baseUrl);

        totalPages = parseInt($('#smogon-pages').val()) || 1;
        if (totalPages < 1) totalPages = 1;

        isRunning = true;
        $('#smogon-start').hide();
        $('#smogon-stop').show();
        postIds = [];
        processedPosts = 0;
        currentPage = 1;

        updateStatus('开始获取帖子列表...');
        fetchPostIds(baseUrl);
    }

    // 停止点赞
    function stopLiking() {
        isRunning = false;
        $('#smogon-start').show();
        $('#smogon-stop').hide();
        updateStatus('操作已停止');
    }

    // 获取帖子ID
    function fetchPostIds(url) {
        if (!isRunning) return;

        updateStatus(`正在获取第 ${currentPage}/${totalPages} 页的帖子...`);

        GM_xmlhttpRequest({
            method: 'GET',
            url: url,
            timeout: config.timeout,
            onload: function(response) {
                if (response.status === 200) {
                    // 解析HTML获取帖子链接
                    const parser = new DOMParser();
                    const doc = parser.parseFromString(response.responseText, 'text/html');

                    // 查找所有帖子链接
                    const postLinks = doc.querySelectorAll('a[href*="/post-"]');
                    const pagePostIds = [];

                    postLinks.forEach(link => {
                        const href = link.getAttribute('href');
                        const postIdMatch = href.match(/\/post-(\d+)/);
                        if (postIdMatch && postIdMatch[1]) {
                            pagePostIds.push(postIdMatch[1]);
                        }
                    });

                    postIds = postIds.concat(pagePostIds);
                    updateStatus(`第 ${currentPage} 页找到 ${pagePostIds.length} 个帖子,总共 ${postIds.length} 个帖子`);

                    if (config.debugMode) {
                        debugLog(`第 ${currentPage} 页帖子ID: ${pagePostIds.join(', ')}`);
                    }

                    // 检查是否是第10页,并查找 "Show older results" 按钮
                    if (currentPage % 10 === 0) {
                        const olderButton = doc.querySelector('a.button[href*="older"]');
                        if (olderButton) {
                            const olderHref = olderButton.getAttribute('href');
                            if (olderHref) {
                                baseUrl = 'https://www.smogon.com' + olderHref;
                                currentPage = 0; // 重置为下一组的第1页
                                updateStatus(`检测到 "Show older results" 链接,切换到更旧结果: ${baseUrl}`);
                                if (config.debugMode) {
                                    debugLog(`切换到 older results URL: ${baseUrl}`);
                                }
                            }
                        }
                    }

                    // 继续下一页
                    if (currentPage < totalPages) {
                        currentPage++;
                        // 构建下一页URL
                        let nextPageUrl;
                        if (baseUrl.includes('?')) {
                            nextPageUrl = baseUrl.replace(/([?&]page=)\d+/, `$1${currentPage}`);
                            if (nextPageUrl === baseUrl) {
                                nextPageUrl = baseUrl + (baseUrl.includes('?') ? '&' : '?') + `page=${currentPage}`;
                            }
                        } else {
                            nextPageUrl = baseUrl + `?page=${currentPage}`;
                        }

                        // 延迟后获取下一页
                        setTimeout(() => fetchPostIds(nextPageUrl), getRandomDelay());
                    } else {
                        // 开始点赞所有帖子
                        updateStatus(`开始点赞 ${postIds.length} 个帖子...`);
                        processNextPost();
                    }
                } else {
                    updateStatus(`错误: 获取页面失败 (HTTP ${response.status})`, 'error');
                    debugLog(`页面响应: ${response.responseText.substring(0, 500)}...`);
                    stopLiking();
                }
            },
            onerror: function(error) {
                updateStatus('错误: 获取页面失败 - ' + error, 'error');
                stopLiking();
            },
            ontimeout: function() {
                updateStatus('错误: 获取页面超时', 'error');
                stopLiking();
            }
        });
    }

    // 处理下一个帖子
    function processNextPost() {
        if (!isRunning || processedPosts >= postIds.length) {
            updateStatus('点赞完成!');
            stopLiking();
            return;
        }

        const postId = postIds[processedPosts];
        updateStatus(`正在处理帖子 ${processedPosts + 1}/${postIds.length} (ID: ${postId})...`);

        // 点赞URL
        const likeUrl = `https://www.smogon.com/forums/posts/${postId}/react?reaction_id=1`;

        GM_xmlhttpRequest({
            method: 'GET',
            url: likeUrl,
            timeout: config.timeout,
            onload: function(response) {
                if (response.status === 200) {
                    if (config.debugMode) {
                        debugLog(`点赞页面响应: ${response.responseText.substring(0, 500)}...`);
                    }

                    // 解析HTML获取CSRF token
                    const parser = new DOMParser();
                    const doc = parser.parseFromString(response.responseText, 'text/html');
                    const csrfToken = doc.querySelector('input[name="_xfToken"]');

                    if (csrfToken) {
                        const token = csrfToken.value;

                        if (config.debugMode) {
                            debugLog(`获取到CSRF Token: ${token.substring(0, 30)}...`);
                        }

                        // 检查是否有Confirm按钮
                        const confirmButton = doc.querySelector('button.button--icon--confirm');
                        if (!confirmButton) {
                            debugLog('未找到Confirm按钮,可能已点赞或无权操作');
                        }

                        // 发送点赞请求
                        sendLikeRequest(likeUrl, token, postId);
                    } else {
                        updateStatus(`错误: 无法获取CSRF token (帖子 ${postId})`, 'error');
                        debugLog(`页面内容: ${response.responseText.substring(0, 500)}...`);
                        processedPosts++;
                        setTimeout(processNextPost, getRandomDelay());
                    }
                } else {
                    updateStatus(`错误: 获取点赞页面失败 (帖子 ${postId}, HTTP ${response.status})`, 'error');
                    processedPosts++;
                    setTimeout(processNextPost, getRandomDelay());
                }
            },
            onerror: function(error) {
                updateStatus(`错误: 获取点赞页面失败 (帖子 ${postId}) - ${error}`, 'error');
                processedPosts++;
                setTimeout(processNextPost, getRandomDelay());
            },
            ontimeout: function() {
                updateStatus(`错误: 获取点赞页面超时 (帖子 ${postId})`, 'error');
                processedPosts++;
                setTimeout(processNextPost, getRandomDelay());
            }
        });
    }

    // 发送点赞请求(移除返回结果检查)
    function sendLikeRequest(url, token, postId) {
        // 准备表单数据
        const formData = new URLSearchParams();
        formData.append('_xfToken', token);
        formData.append('_xfWithData', '1');
        formData.append('_xfResponseType', 'json');

        GM_xmlhttpRequest({
            method: 'POST',
            url: url,
            data: formData.toString(),
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
                'X-Requested-With': 'XMLHttpRequest'
            },
            timeout: config.timeout,
            onload: function(response) {
                if (config.debugMode) {
                    debugLog(`点赞响应: ${response.responseText}`);
                }

                if (response.status === 200) {
                    updateStatus(`成功点赞帖子 ${postId} (HTTP 200)`);
                } else {
                    updateStatus(`点赞失败 (帖子 ${postId}, HTTP ${response.status})`, 'error');
                    debugLog(`响应内容: ${response.responseText.substring(0, 500)}...`);
                }

                processedPosts++;
                setTimeout(processNextPost, getRandomDelay());
            },
            onerror: function(error) {
                updateStatus(`错误: 点赞请求失败 (帖子 ${postId}) - ${error}`, 'error');
                processedPosts++;
                setTimeout(processNextPost, getRandomDelay());
            },
            ontimeout: function() {
                updateStatus(`错误: 点赞请求超时 (帖子 ${postId})`, 'error');
                processedPosts++;
                setTimeout(processNextPost, getRandomDelay());
            }
        });
    }

    // 获取随机延迟
    function getRandomDelay() {
        return Math.floor(Math.random() * (config.maxDelay - config.minDelay + 1)) + config.minDelay;
    }

    // 更新状态
    function updateStatus(message, type = 'info') {
        const status = $('#smogon-status');
        const now = new Date().toLocaleTimeString();
        const color = type === 'error' ? '#e74c3c' : (type === 'success' ? '#2ecc71' : '#34495e');

        status.prepend(`<div style="color: ${color}">[${now}] ${message}</div>`);

        // 限制显示的行数
        const lines = status.children();
        if (lines.length > 20) {
            lines.last().remove();
        }

        // 限制显示的行数
        if (config.debugMode) {
            console.log(`[SmogonAutoLiker] ${message}`);
        }
    }

    // 调试日志
    function debugLog(message) {
        if (config.debugMode) {
            console.log(`[SmogonAutoLiker DEBUG] ${message}`);

            // 也显示在状态区域
            const status = $('#smogon-status');
            const now = new Date().toLocaleTimeString();
            status.prepend(`<div style="color: #7f8c8d; font-size: 12px;">[${now} DEBUG] ${message}</div>`);

            // 限制显示的行数
            const lines = status.children();
            if (lines.length > 25) {
                lines.slice(25).remove();
            }
        }
    }

    // 初始化
    $(document).ready(function() {
        // 等待页面加载完成
        setTimeout(createUI, 1000);
    });
})();