Neopets bot shop reporter

Detects bot accounts

目前為 2025-07-01 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Neopets bot shop reporter
// @namespace    http://tampermonkey.net/
// @version      2.5
// @description  Detects bot accounts
// @author       God
// @match        *://www.neopets.com/browseshop.phtml?owner=*
// @match        *://www.neopets.com/settings/privacy/report/?offender=*
// @grant        GM_xmlhttpRequest
// @grant        window.focus
// @connect      www.neopets.com
// @connect      images.neopets.com
// ==/UserScript==

(function() {
    'use strict';

    // Banner image URL for bot accounts
    const BOT_BANNER_URL = 'https://images.neopets.com/new_shopkeepers/0.gif';
    let hasSubmitted = false; // Flag to prevent multiple submissions
    const MAX_RETRIES = 3; // Max retries for submission
    const SUBMISSION_TIMEOUT = 10000; // Increased timeout for submission (10s)
    const REPORT_DELAY = 5000; // Delay between reports (5s)

    // Function to observe DOM for an element (using CSS or XPath) and execute callback when found
    function observeForElement(selector, callback, targetNode = document.body, timeout = 30000, isXPath = false) {
        if (!targetNode) {
            console.warn(`Target node not found for ${selector}. Retrying...`);
            setTimeout(() => observeForElement(selector, callback, document.body, timeout, isXPath), 100);
            return;
        }

        let timeoutId;
        const observer = new MutationObserver((mutations, obs) => {
            let element;
            if (isXPath) {
                const result = document.evaluate(selector, targetNode, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
                element = result.singleNodeValue;
            } else {
                element = targetNode.querySelector(selector);
            }
            if (element) {
                obs.disconnect();
                clearTimeout(timeoutId);
                callback(element);
            }
        });

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

        // Immediate check
        let element;
        if (isXPath) {
            const result = document.evaluate(selector, targetNode, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
            element = result.singleNodeValue;
        } else {
            element = targetNode.querySelector(selector);
        }
        if (element) {
            observer.disconnect();
            callback(element);
            return;
        }

        // Timeout
        timeoutId = setTimeout(() => {
            observer.disconnect();
            console.error(`Timeout: Element ${selector} not found after ${timeout}ms`);
            console.log('App div content:', targetNode.querySelector('#app')?.innerHTML || 'Empty');
            console.log('All forms:', Array.from(targetNode.querySelectorAll('form')).map(form => form.outerHTML));
            console.log('Form inputs:', Array.from(targetNode.querySelectorAll('input')).map(el => ({ id: el.id, name: el.name, type: el.type, class: el.className, value: el.value })));
            console.log('Form selects:', Array.from(targetNode.querySelectorAll('select')).map(el => ({ id: el.id, name: el.name, class: el.className, value: el.value, html: el.outerHTML, options: Array.from(el.options).map(opt => ({ value: opt.value, text: opt.text })) })));
            console.log('Form textareas:', Array.from(targetNode.querySelectorAll('textarea')).map(el => ({ id: el.id, name: el.name, class: el.className, value: el.value })));
            console.log('Form buttons:', Array.from(targetNode.querySelectorAll('button')).map(el => ({ id: el.id, name: el.name, type: el.type, class: el.className })));
            const captcha = targetNode.querySelector('form[id*="captcha"], div[id*="captcha"], [class*="captcha"]');
            if (captcha) {
                console.warn('Possible CAPTCHA or anti-bot prompt detected:', captcha.outerHTML);
            }
            callback(null);
        }, timeout);
    }

    // Function to check if user is logged in
    function isLoggedIn() {
        return !!document.querySelector('a[href="/logout.phtml"]');
    }

    // Function to extract username from shop or report page
    function getUsername() {
        const url = window.location.href;
        if (url.includes('browseshop.phtml')) {
            const reportLink = document.querySelector('a[href*="/autoform_abuse.phtml?abuse=report"]');
            if (reportLink) {
                const match = reportLink.getAttribute('href').match(/offender=([^&]+)/);
                return match ? match[1] : null;
            }
            return null;
        } else if (url.includes('settings/privacy/report')) {
            const urlParams = new URLSearchParams(window.location.search);
            return urlParams.get('offender');
        }
        return null;
    }

    // Function to log form state for debugging
    function logFormState(form, stage) {
        console.log(`Form state at ${stage}:`);
        console.log('Inputs:', Array.from(form.querySelectorAll('input')).map(el => ({ id: el.id, name: el.name, type: el.type, class: el.className, value: el.value, checked: el.checked })));
        console.log('Selects:', Array.from(form.querySelectorAll('select')).map(el => ({ id: el.id, name: el.name, class: el.className, value: el.value, options: Array.from(el.options).map(opt => ({ value: opt.value, text: opt.text })) })));
        console.log('Textareas:', Array.from(form.querySelectorAll('textarea')).map(el => ({ id: el.id, name: el.name, class: el.className, value: el.value })));
        console.log('Buttons:', Array.from(form.querySelectorAll('button')).map(el => ({ id: el.id, name: el.name, type: el.type, class: el.className })));
    }

    // Function to check for bot banner in DOM
    function checkBotBanner(callback) {
        observeForElement('div[align="center"] img[src*="new_shopkeepers"]', (img) => {
            if (!img) {
                console.log('No shop banner image found in div[align="center"]. Skipping shop.');
                callback(false);
                return;
            }
            const src = img.getAttribute('src');
            console.log(`Found shop banner: ${src}`);
            if (src === BOT_BANNER_URL) {
                console.log('Bot banner (0.gif) detected.');
                callback(true);
            } else {
                console.log(`Non-bot banner detected (${src}). Skipping shop.`);
                callback(false);
            }
        }, document.body, 15000); // Increased timeout for slow-loading pages
    }

    // Function to fill and submit the report form
    function fillReportForm(username, reportWindow = window, retryCount = 0) {
        if (hasSubmitted) {
            console.log(`Form already submitted for ${username}. Skipping.`);
            return;
        }
        console.log(`Filling report form for user: ${username} (Attempt ${retryCount + 1}/${MAX_RETRIES})`);
        console.log(`Report type from URL: ${new URLSearchParams(reportWindow.location.search).get('reportType') || 'None'}`);
        console.log(`Initial window location: ${reportWindow.location.href}`);

        // Capture JavaScript errors
        reportWindow.onerror = function(message, source, lineno, colno, error) {
            console.error(`Page JavaScript error: ${message} at ${source}:${lineno}:${colno}`, error);
        };

        // Try to focus the window
        try {
            reportWindow.focus();
        } catch (e) {
            console.warn('Failed to focus report window:', e.message);
        }

        // Wait for form to load in #app
        observeForElement('#app form', (form) => {
            if (!form) {
                console.error('Report form not found in #app. Retrying or manual intervention required.');
                if (retryCount < MAX_RETRIES - 1) {
                    setTimeout(() => fillReportForm(username, reportWindow, retryCount + 1), REPORT_DELAY);
                } else {
                    console.error('Max retries reached for form detection. Please fill the form manually.');
                }
                return;
            }
            console.log('Report form found:', form.outerHTML);
            logFormState(form, 'pre-fill');

            // Add submit event listener for debugging
            form.addEventListener('submit', () => {
                console.log('Form submit event triggered');
                console.log('Form data:', Array.from(new FormData(form).entries()).map(([key, value]) => ({ key, value })));
            });

            // Check no_user checkbox
            observeForElement('#no_user', (checkbox) => {
                if (!checkbox) {
                    console.error('No_user checkbox not found, trying XPath');
                    observeForElement('//input[@id="no_user" or @name="no_user"]', (xpathCheckbox) => {
                        if (!xpathCheckbox) {
                            console.error('No_user checkbox not found with XPath. Please fill the form manually.');
                            return;
                        }
                        handleCheckbox(xpathCheckbox);
                    }, reportWindow.document, 30000, true);
                    return;
                }
                handleCheckbox(checkbox);
            }, reportWindow.document, 30000);

            function handleCheckbox(checkbox) {
                if (!checkbox.checked) {
                    checkbox.checked = true;
                    checkbox.dispatchEvent(new Event('change', { bubbles: true }));
                    console.log('Checked no_user checkbox');
                }

                // Select Other from report_type
                observeForElement('#report_type', (select) => {
                    if (!select) {
                        console.error('Report_type dropdown not found, trying XPath');
                        observeForElement('//select[@id="report_type" or @name="report_type"]', (xpathSelect) => {
                            if (!xpathSelect) {
                                console.error('Report_type dropdown not found with XPath. Please fill the form manually.');
                                return;
                            }
                            handleSelect(xpathSelect, 'report_type');
                        }, reportWindow.document, 30000, true);
                        return;
                    }
                    handleSelect(select, 'report_type');
                }, reportWindow.document, 30000);

                function handleSelect(select, type) {
                    const options = Array.from(select.options).map(opt => ({ value: opt.value, text: opt.text }));
                    console.log(`${type} options:`, options);
                    console.log(`${type} HTML:`, select.outerHTML);
                    if (select.querySelector('option[value="Other"]')) {
                        select.value = 'Other';
                        select.dispatchEvent(new Event('change', { bubbles: true }));
                        console.log(`Selected Other in ${type}`);
                    } else {
                        console.error(`Option "Other" not found in ${type}, selecting last option as fallback`);
                        select.value = options[options.length - 1].value;
                        select.dispatchEvent(new Event('change', { bubbles: true }));
                        console.log(`Selected fallback option: ${select.value}`);
                    }

                    // Select Other from report_place if report_type was successful
                    if (type === 'report_type') {
                        observeForElement('#report_place', (placeSelect) => {
                            if (!placeSelect) {
                                console.error('Report_place dropdown not found, trying XPath');
                                observeForElement('//select[@id="report_place" or @name="report_place"]', (xpathPlaceSelect) => {
                                    if (!xpathPlaceSelect) {
                                        console.error('Report_place dropdown not found with XPath. Please fill the form manually.');
                                        return;
                                    }
                                    handleSelect(xpathPlaceSelect, 'report_place');
                                }, reportWindow.document, 30000, true);
                                return;
                            }
                            handleSelect(placeSelect, 'report_place');
                        }, reportWindow.document, 30000);
                    }

                    // Try to fill textarea, but proceed if not found
                    if (type === 'report_place') {
                        observeForElement('#report_body', (textarea) => {
                            if (!textarea) {
                                console.error('Report_body textarea not found, trying XPath');
                                observeForElement('//textarea[@id="report_body" or @name="report_body"]', (xpathTextarea) => {
                                    if (!xpathTextarea) {
                                        console.warn('No textarea found, attempting submission without report_body');
                                        submitForm(form);
                                        return;
                                    }
                                    handleTextarea(xpathTextarea);
                                }, reportWindow.document, 5000, true);
                                return;
                            }
                            handleTextarea(textarea);
                        }, reportWindow.document, 5000);
                    }
                }

                function handleTextarea(textarea) {
                    const comment = getReportComment();
                    textarea.value = comment;
                    textarea.dispatchEvent(new Event('input', { bubbles: true }));
                    console.log('Filled report_body with comment:', comment);
                    submitForm(form);
                }

                function submitForm(form) {
                    observeForElement('#email_btn', (button) => {
                        if (!button) {
                            console.error('Submit button not found, trying XPath');
                            observeForElement('//button[@id="email_btn" or @name="email_btn" or @type="submit"]', (xpathButton) => {
                                if (!xpathButton) {
                                    console.error('Submit button not found with XPath. Please submit the form manually.');
                                    return;
                                }
                                handleSubmit(form, xpathButton);
                            }, reportWindow.document, 30000, true);
                            return;
                        }
                        handleSubmit(form, button);
                    }, reportWindow.document, 30000);
                }

                function handleSubmit(form, button, submitRetryCount = 0) {
                    if (hasSubmitted) {
                        console.log('Form already submitted. Skipping.');
                        return;
                    }
                    hasSubmitted = true;
                    console.log(`Submitting report (Attempt ${submitRetryCount + 1}/${MAX_RETRIES})`);
                    logFormState(form, 'pre-submit');

                    setTimeout(() => {
                        try {
                            button.click();
                            console.log('Submit button clicked');
                        } catch (e) {
                            console.error('Error clicking submit button:', e.message);
                        }

                        setTimeout(() => {
                            console.log(`Post-submit window location: ${reportWindow.location.href}`);
                            const successDiv = reportWindow.document.querySelector('#submitResponse_report');
                            if (successDiv && successDiv.textContent.includes('Your request has been successfully submitted!')) {
                                console.log('Report submission successful for', username);
                                logFormState(form, 'post-submit-success');
                                // Log reported user
                                let reportedUsers = GM_getValue('reportedUsers', []);
                                reportedUsers.push({ username, timestamp: new Date().toISOString() });
                                GM_setValue('reportedUsers', reportedUsers);
                                console.log('Logged reported user:', username);
                                // Attempt to close tab
                                setTimeout(() => {
                                    try {
                                        reportWindow.close();
                                        console.log('Report tab closed');
                                    } catch (e) {
                                        console.warn('Failed to close report tab:', e.message);
                                        console.log('Please close the report tab manually.');
                                    }
                                }, 1000);
                            } else {
                                console.log('No success message found for', username);
                                console.log('Report page HTML after submission:', reportWindow.document.documentElement.outerHTML);
                                logFormState(form, 'post-submit-failure');
                                const errorMsg = reportWindow.document.querySelector('.error, .alert, [class*="error"], [class*="alert"]');
                                if (errorMsg) {
                                    console.error('Possible submission error detected:', errorMsg.outerHTML);
                                }
                                const captcha = reportWindow.document.querySelector('form[id*="captcha"], div[id*="captcha"], [class*="captcha"]');
                                if (captcha) {
                                    console.warn('CAPTCHA detected. Please complete it manually and resubmit.');
                                    hasSubmitted = false;
                                    return;
                                }
                                if (!reportWindow.location.href.includes('settings/privacy/report')) {
                                    console.error('Possible redirect detected after submission:', reportWindow.location.href);
                                }
                                hasSubmitted = false;
                                if (submitRetryCount < MAX_RETRIES - 1) {
                                    console.log('Retrying submission...');
                                    setTimeout(() => handleSubmit(form, button, submitRetryCount + 1), REPORT_DELAY);
                                } else {
                                    console.error('Max submission retries reached. Please submit the form manually.');
                                }
                            }
                        }, SUBMISSION_TIMEOUT);
                    }, REPORT_DELAY);
                }
            }
        }, reportWindow.document, 30000);
    }

    // Function to generate randomized report comment
    function getReportComment() {
        const comments = [
            "This is a bot account with a default shop banner and minimal shop activity, likely used for automated tasks.",
            "Suspected bot with no shop customization and low item count, possibly for dailies or betting.",
            "This account appears to be a bot shell with a default banner and small shop."
        ];
        return comments[Math.floor(Math.random() * comments.length)];
    }

    // Main function to handle both shop and report pages
    function main() {
        console.log('Neopets Shop Report Autofill script started at', new Date().toLocaleString('en-AU', { timeZone: 'Australia/Sydney' }));

        // Check for conflicting scripts
        if (window._tampermonkey || window.GM_info) {
            console.warn('Multiple Tampermonkey scripts detected. This may cause conflicts.');
        }

        // Check if user is logged in
        if (!isLoggedIn()) {
            console.error('User not logged in. Please log in to Neopets.');
            return;
        }

        const url = window.location.href;
        if (url.includes('browseshop.phtml')) {
            // Shop page: check for bot banner and item conditions
            checkBotBanner((isBotBanner) => {
                if (!isBotBanner) {
                    console.log('No bot banner detected. Skipping shop.');
                    return;
                }

                // Check for item table or no-items message
                observeForElement('table[align="center"][border="0"][cellpadding="3"], span[style="color: black;"] > b', (element) => {
                    let shouldReport = false;
                    if (element.tagName.toLowerCase() === 'table') {
                        const items = element.querySelectorAll('td').length / 2; // Each item has 2 td elements
                        console.log(`Found ${items} items in shop`);
                        if (items <= 20) {
                            shouldReport = true;
                        }
                    } else if (element.textContent.includes('There are no items for sale in this shop!')) {
                        console.log('No items in shop');
                        shouldReport = true;
                    }

                    if (shouldReport) {
                        const username = getUsername();
                        if (username) {
                            console.log(`Reporting user: ${username}`);
                            const reportUrl = `https://www.neopets.com/settings/privacy/report/?offender=${encodeURIComponent(username)}&reportType=Shops`;
                            console.log(`Opening report URL in new tab: ${reportUrl}`);
                            const reportWindow = window.open(reportUrl, '_blank');
                            if (!reportWindow) {
                                console.error('Failed to open report page. Please allow pop-ups.');
                                return;
                            }
                            // Wait for the report page to load
                            const checkWindowLoaded = setInterval(() => {
                                if (reportWindow.document.readyState === 'complete') {
                                    clearInterval(checkWindowLoaded);
                                    console.log(`Report page loaded for ${username}`);
                                    try {
                                        reportWindow.focus();
                                    } catch (e) {
                                        console.warn('Failed to focus report window:', e.message);
                                    }
                                    fillReportForm(username, reportWindow);
                                }
                            }, 500);
                        } else {
                            console.error('Could not extract username from report link');
                        }
                    } else {
                        console.log('Shop has more than 20 items, not reporting');
                    }
                }, document.body, 15000);
            });
        } else if (url.includes('settings/privacy/report')) {
            // Report page: directly fill the form
            const username = getUsername();
            if (username) {
                console.log(`Report type from URL: ${new URLSearchParams(window.location.search).get('reportType') || 'None'}`);
                fillReportForm(username);
            } else {
                console.error('No offender username found in URL');
            }
        }
    }

    // Start script when page loads
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', main);
    } else {
        main();
    }
})();