Neopets Main Shop Aber

Aber for main neopets shops. Buys at realistic human speed. never banned using this. Change the settings to your own liking.

// ==UserScript==
// @name         Neopets Main Shop Aber
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Aber for main neopets shops. Buys at realistic human speed. never banned using this. Change the settings to your own liking.
// @license      MIT
// @author       God
// @match        https://www.neopets.com/objects.phtml?obj_type=*
// @match        https://www.neopets.com/objects.phtml?type=*
// @match        https://www.neopets.com/haggle.phtml*
// @grant        GM_xmlhttpRequest
// @connect      cdn.jsdelivr.net
// ==/UserScript==

(function() {
    'use strict';

    // Configuration for all delays (in milliseconds)
    const delays = {
        confirmation: 500, // Delay after clicking an item to wait for confirmation popup
        inventoryMessage: 5000, // Delay after confirming purchase to check for inventory message
        shopRedirect: 2000, // Delay before redirecting to shop after purchase or SOLD OUT (no longer used for instant redirect)
        refreshMin: 2000, // Minimum delay for shop page refresh
        refreshMax: 4000, // Maximum delay for shop page refresh
        captcha: 500, // Delay after setting haggle offer to click CAPTCHA
        waitForElementTimeout: 450, // Timeout for each attempt to wait for an element
        waitForElementRetryDelay: 450, // Delay between retry attempts for waitForElement
        waitForElementMaxRetries: 3, // Maximum retries for waitForElement (for #shopkeeper_makes_deal)
        successMessageRetryDelay: 1000, // Delay between retries for success message detection
        successMessageMaxRetries: 3 // Maximum retries for success message detection
    };

    // List of item names to target (updated list)
    const targetItems = [
        'Mythical Xweetok Hind',
        'Mythical Xweetok Body',
        'Mythical Xweetok Head',
    ];

    // Shop URL to redirect to after purchase or SOLD OUT
    const shopUrl = 'https://www.neopets.com/objects.phtml?type=shop&obj_type=4'; // <Change the shop ID here currently shop 4

    let Tesseract;
    let isScriptRunning = true; // Global flag to control script execution
    let refreshTimeout = null; // To track and cancel the refresh timer

    // Function to simulate a mouse click at specific coordinates on an element
    function simulateClick(element, x, y) {
        if (!element) return false;
        try {
            const rect = element.getBoundingClientRect();
            const clientX = x !== undefined ? rect.left + x : rect.left + rect.width / 2;  // Default to center if x not provided
            const clientY = y !== undefined ? rect.top + y : rect.top + rect.height / 2;   // Default to center if y not provided
            const event = new MouseEvent('click', {
                bubbles: true,
                cancelable: true,
                view: unsafeWindow || window,  // Use unsafeWindow to bypass sandboxing
                clientX: clientX,
                clientY: clientY
            });
            element.dispatchEvent(event);
            return true;
        } catch (error) {
            return false;
        }
    }

    // Function to get random percentage between min and max (e.g., 1.01 to 1.04)
    function getRandomPercentage(min, max) {
        return Math.random() * (max - min) + min;
    }

    // Function to get random delay between min and max milliseconds
    function getRandomDelay(min, max) {
        return Math.floor(Math.random() * (max - min + 1)) + min;
    }

    // Function to wait for an element to appear in the DOM with retries
    function waitForElement(selector, timeout = delays.waitForElementTimeout, maxRetries = delays.waitForElementMaxRetries) {
        return new Promise((resolve, reject) => {
            let attempt = 0;
            const tryFindElement = () => {
                const element = document.querySelector(selector);
                if (element) {
                    resolve(element);
                    return;
                }

                const observer = new MutationObserver((mutations, obs) => {
                    const element = document.querySelector(selector);
                    if (element) {
                        obs.disconnect();
                        resolve(element);
                    }
                });
                observer.observe(document.body, { childList: true, subtree: true });

                setTimeout(() => {
                    observer.disconnect();
                    attempt++;
                    if (attempt < maxRetries) {
                        setTimeout(tryFindElement, delays.waitForElementRetryDelay);
                    } else {
                        reject(new Error(`Element ${selector} not found after ${maxRetries} attempts`));
                    }
                }, timeout);
            };
            tryFindElement();
        });
    }

    // Function to scroll to the bottom of the page
    function scrollToBottom() {
        window.scrollTo({
            top: document.body.scrollHeight,
            behavior: 'smooth'
        });
    }

    // Function to create a stop/start button on the shop page
    function createToggleButton() {
        const button = document.createElement('button');
        button.id = 'toggleScriptButton';
        button.textContent = 'Stop Script';
        button.style.position = 'fixed';
        button.style.top = '10px';
        button.style.right = '10px';
        button.style.zIndex = '9999';
        button.style.padding = '10px';
        button.style.backgroundColor = '#ff4444';
        button.style.color = 'white';
        button.style.border = 'none';
        button.style.borderRadius = '5px';
        button.style.cursor = 'pointer';
        button.style.fontWeight = 'bold';

        button.addEventListener('click', () => {
            isScriptRunning = !isScriptRunning;
            button.textContent = isScriptRunning ? 'Stop Script' : 'Start Script';
            button.style.backgroundColor = isScriptRunning ? '#ff4444' : '#44ff44';

            // Clear the refresh timer if stopping the script
            if (!isScriptRunning && refreshTimeout) {
                clearTimeout(refreshTimeout);
                refreshTimeout = null;
            }
        });

        document.body.appendChild(button);
        return button;
    }

    // Function to find and click target items on the shop page
    function findAndClickItems() {
        // Add the stop/start button on the shop page
        const button = document.getElementById('toggleScriptButton') || createToggleButton();

        // Scroll to the bottom of the page on shop page
        scrollToBottom();

        // Only proceed if the script is running
        if (!isScriptRunning) {
            return false;
        }

        const shopItems = document.querySelectorAll('.shop-item .item-img');
        for (const item of shopItems) {
            const itemName = item.getAttribute('data-name');
            if (targetItems.includes(itemName)) {
                if (simulateClick(item)) {
                    // Wait for the popup to appear and simulate click on confirm button
                    setTimeout(() => {
                        const confirmButton = document.evaluate(
                            '//button[@id="confirm-link"]',
                            document,
                            null,
                            XPathResult.FIRST_ORDERED_NODE_TYPE,
                            null
                        ).singleNodeValue;
                        if (confirmButton && simulateClick(confirmButton)) {
                            // Check for item added to inventory message, haggle success, sold out, or error page
                            setTimeout(() => {
                                const inventoryMessage = document.querySelector('p b')?.textContent?.trim();
                                const pageMessage = document.querySelector('p')?.textContent?.trim().toLowerCase();

                                // Check for successful purchase (inventory message)
                                if (inventoryMessage && targetItems.includes(inventoryMessage) && pageMessage.includes('has been added to your inventory')) {
                                    window.location.href = shopUrl;
                                    return; // Stop further execution
                                } else if (pageMessage.includes('the shopkeeper says \'i accept your offer of')) {
                                    // Check for haggle success message
                                    window.location.href = shopUrl;
                                    return; // Stop further execution
                                } else if (pageMessage.includes('is sold out!')) {
                                    // Check for "SOLD OUT!" message on shop page
                                    window.location.href = shopUrl;
                                    return; // Stop further execution
                                } else if (pageMessage.includes('too quickly') || pageMessage.includes('slow down')) {
                                    // Check for "too quickly" error page
                                    window.location.href = shopUrl;
                                    return; // Stop further execution
                                } else {
                                    // If none of the above conditions are met, proceed with haggle page logic
                                    if (window.location.href.includes('haggle.phtml')) {
                                        // Check if the URL has additional parameters indicating a true haggle page
                                        const urlParams = new URLSearchParams(window.location.search);
                                        if (urlParams.toString().length > 0) {
                                            handleHagglePage();
                                        } else {
                                            window.location.href = shopUrl;
                                        }
                                    } else {
                                        window.location.href = shopUrl;
                                    }
                                }
                            }, delays.inventoryMessage); // Dynamic delay for inventory message check
                        }
                    }, delays.confirmation); // Dynamic delay for confirmation popup
                    return true; // Exit after clicking one item to avoid multiple popups
                } else {
                    return false;
                }
            }
        }
        return false;
    }

    // Function to retry finding a message with a delay
    async function retryFindMessage(checkMessage, maxRetries = delays.successMessageMaxRetries, retryDelay = delays.successMessageRetryDelay) {
        let attempt = 0;
        while (attempt < maxRetries) {
            const pageMessage = document.querySelector('p')?.textContent?.trim().toLowerCase();
            if (pageMessage && pageMessage.includes(checkMessage)) {
                return true;
            }
            attempt++;
            if (attempt < maxRetries) {
                await new Promise(resolve => setTimeout(resolve, retryDelay));
            }
        }
        return false;
    }

    // Function to handle haggling on the haggle page
    async function handleHagglePage() {
        // Check for SOLD OUT message first
        const soldOutMessage = Array.from(document.querySelectorAll('p')).some(p => p.textContent.includes('SOLD OUT!'));
        if (soldOutMessage) {
            window.location.href = shopUrl;
            return;
        }

        // Check for haggle success message with retries
        const foundSuccessMessage = await retryFindMessage('the shopkeeper says \'i accept your offer of');
        if (foundSuccessMessage) {
            window.location.href = shopUrl;
            return;
        }

        // Wait for the shopkeeper message element to appear
        let shopkeeperDiv;
        try {
            shopkeeperDiv = await waitForElement('#shopkeeper_makes_deal');
        } catch (error) {
            // Assume successful purchase since #shopkeeper_makes_deal is not found
            window.location.href = shopUrl;
            return;
        }

        // Try to find the message within the container
        const shopkeeperMessageElement = shopkeeperDiv.querySelector('b') || shopkeeperDiv.querySelector('p') || shopkeeperDiv;
        const shopkeeperMessage = shopkeeperMessageElement?.textContent || '';

        // Attempt to extract the minimum price with broader regex patterns
        const priceMatch = shopkeeperMessage.match(/I want at least ([\d,]+) Neopoints/) ||
                         shopkeeperMessage.match(/I wont take less than ([\d,]+) Neopoints/) ||
                         shopkeeperMessage.match(/at least ([\d,]+) Neopoints/) ||
                         shopkeeperMessage.match(/([\d,]+) Neopoints/);
        if (!priceMatch) {
            window.location.href = shopUrl;
            return;
        }

        // Parse the minimum price and remove commas
        const minPrice = parseInt(priceMatch[1].replace(/,/g, ''), 10);
        if (isNaN(minPrice)) {
            window.location.href = shopUrl;
            return;
        }

        // Calculate offer: 1.01 to 1.04 times the minimum price
        const multiplier = getRandomPercentage(1.01, 1.04);
        const offerPrice = Math.round(minPrice * multiplier);

        // Set the offer in the input field
        const offerInput = document.querySelector('input[name="current_offer"]');
        if (offerInput) {
            offerInput.value = offerPrice.toString();
        } else {
            window.location.href = shopUrl;
            return;
        }

        // Load Tesseract.js if not already loaded (only for haggle page CAPTCHA)
        if (!Tesseract) {
            await new Promise((resolve) => {
                const script = document.createElement('script');
                script.src = 'https://cdn.jsdelivr.net/npm/tesseract.js@2/dist/tesseract.min.js';
                script.onload = () => {
                    Tesseract = window.Tesseract;
                    resolve();
                };
                document.head.appendChild(script);
            });
        }

        // Simulate click on the CAPTCHA image (Haggle button) targeting the Neopet using OCR
        setTimeout(async () => {
            const haggleButton = document.querySelector('input[type="image"]');
            if (haggleButton) {
                // Convert image to canvas for OCR analysis
                const canvas = document.createElement('canvas');
                const context = canvas.getContext('2d');
                const img = new Image();
                img.crossOrigin = 'anonymous'; // Handle CORS if needed
                img.src = haggleButton.src;
                await new Promise(resolve => img.onload = resolve);

                canvas.width = img.width;
                canvas.height = img.height;
                context.drawImage(img, 0, 0);

                // Use pixel data to detect dark areas (approximate Neopet position)
                const { data } = context.getImageData(0, 0, canvas.width, canvas.height);
                let darkestX = 0, darkestY = 0, darkestValue = 255 * 3; // RGB max sum
                for (let y = 0; y < canvas.height; y++) {
                    for (let x = 0; x < canvas.width; x++) {
                        const index = (y * canvas.width + x) * 4;
                        const r = data[index];
                        const g = data[index + 1];
                        const b = data[index + 2];
                        const brightness = r + g + b; // Sum of RGB for darkness
                        if (brightness < darkestValue) {
                            darkestValue = brightness;
                            darkestX = x;
                            darkestY = y;
                        }
                    }
                }

                const rect = haggleButton.getBoundingClientRect();
                const clickX = darkestX * (rect.width / canvas.width);
                const clickY = darkestY * (rect.height / canvas.height);

                if (!simulateClick(haggleButton, clickX, clickY)) {
                    window.location.href = shopUrl;
                }
            } else {
                window.location.href = shopUrl;
            }
        }, delays.captcha); // Dynamic delay for CAPTCHA click
    }

    // Main function to run the script
    function main() {
        const currentUrl = window.location.href;

        // Handle shop pages
        if (currentUrl.includes('objects.phtml')) {
            if (!findAndClickItems()) {
                // If no items found, set up a MutationObserver to watch for dynamic content
                const observer = new MutationObserver((mutations) => {
                    if (findAndClickItems()) {
                        observer.disconnect(); // Stop observing after successful click
                    }
                });
                observer.observe(document.body, { childList: true, subtree: true });

                // Schedule a refresh if no items are found after a random delay
                if (isScriptRunning) {
                    refreshTimeout = setTimeout(() => {
                        // Double-check if items appeared before refreshing
                        if (!findAndClickItems()) {
                            window.location.reload();
                        }
                    }, getRandomDelay(delays.refreshMin, delays.refreshMax)); // Dynamic refresh delay range
                }
            }
        }
        // Handle haggle page
        else if (currentUrl.includes('haggle.phtml')) {
            if (typeof handleHagglePage === 'function') {
                handleHagglePage();
            } else {
                console.error('handleHagglePage is not defined, redirecting to shop page as fallback');
                window.location.href = shopUrl;
            }
        }
    }

    // Run the script after the page loads
    window.addEventListener('load', main);
})();